Csharp — 字符串截取,从Substring到Span

经典之选:String.Substring方法

Substring 是大多数 C# 开发者接触的第一个字符串截取方法,它简单直接

基本用法

CSHARP
string text = "0123456789";

// 从指定位置截取到末位
string part1 = text.Substring(7); // "789"

// 从指定位置截取指定长度,从0开始,数5个字符
string part2 = text.Substring(0, 5); // "01234"
点击展开查看更多

需要注意边界检查,如果过界的话会提示异常:ArgumentOutOfRangeException

CSHARP
// 这些调用都会抛出异常
// string error1 = text.Substring(20); // 起始位置超出长度
// string error2 = text.Substring(0, 20); // 长度超出范围

/// <summary>
/// 安全地截取字符串
/// </summary>
/// <param name="input">字符串</param>
/// <param name="startIndex">起始点</param>
/// <param name="length">字符串长度</param>
/// <returns></returns>
public static string SafeSubstring(string input, int startIndex, int length = -1)
{
    if (string.IsNullOrEmpty(input) || startIndex >= input.Length)
        return string.Empty;
    
    if (length == -1)
        return input.Substring(startIndex);

    return input.Substring(startIndex, Math.Min(length, input.Length - startIndex));
        
}
点击展开查看更多

灵活利器:Range 与 Index(C# 8.0+)

C# 8.0引入了更简洁的语法,让字符串截取变得优雅如Python

Range

基本使用

CSHARP
string text = "0123456789";

// 使用 Range 操作符
string start = text[..7]; // "0123456"
string end = text[3..]; // "3456789"
string middle = text[5..7]; // "56"
string all = text[..]; // "0123456789"
点击展开查看更多

Index

Index类型主要优势在于从后往前计数的便利性

基本使用

CSHARP
string text2 = "Programming is fun!";

// 传统方式获取最后3个字符
string last3Traditional = text2.Substring(text2.Length - 3); // "un!"

// 使用Index方式
string last3Index = text2[^3..]; // "un!"

// 获取除最后3个字符外的所有内容
string withoutLast3 = text2[..^3]; // Programming is f
点击展开查看更多

Programming is fun!

Span 与 Memory

在处理大量字符串或高性能场景时,Substring 会创建新的字符串对象,可能成为性能瓶颈。这时就该 Span<T> 登场了!

This is a very long text that needs efficient processing

基本使用

CSHARP
string longText = "This is a very long text that needs efficient processing";

// 传统方式 - 创建新字符串(有内存分配)
string substring = longText.Substring(10, 15); // 创建新对象

// 使用Span - 零内存分配!
ReadOnlySpan<char> textSpan = longText.AsSpan();
ReadOnlySpan<char> slice = textSpan.Slice(10, 15); // 只是视图,无内存分配

// 转换为字符串(需要时才分配内存)
string result = slice.ToString(); // "very long text"
点击展开查看更多

SubstringSpan 的性能对比

CSHARP
// 处理10000个字符串片段
var list = new List<string>();

// 方式1:使用Substring(创建10000个新字符串)
foreach (string item in stringArray)
{
    list.Add(item.Substring(5, 10));
}

// 方式2:使用Span(零内存分配直到需要字符串时)
foreach (string item in stringArray)
{
    var span = item.AsSpan().Slice(5, 10);
    // 延迟字符串创建,只在必要时调用ToString()
    list.Add(span.ToString());
}
点击展开查看更多

内存分配的故事 —— 为什么 Span 性能比 Substring 性能要好?

打个比方,比如你想去图书馆阅读《代码大全》第10页到第24页的内容(原始字符串)。

CSHARP
string books = "This is a very long text that needs efficient processing";
// 想象books就是《代码大全》,我们想阅读"very long text"这部分
点击展开查看更多

使用传统的方式阅读第10页到第24页的内容(Substring)

CSHARP
// 传统方式 - 创建新字符串(有内存分配)
string substring = longText.Substring(10, 15); // "very long text"
点击展开查看更多

这相当于:

  1. 你去图书馆找到这本书
  2. 把第10页到第24页的内容复印下来
  3. 带着复印件回家

结果:消耗了纸张(内存),创建了完整的副本

Span方式:书签定位(零内存分配)

CSHARP
// 使用Span - 零内存分配!
ReadOnlySpan<char> textSpan = longText.AsSpan();
ReadOnlySpan<char> slice = textSpan.Slice(10, 15); // 只是视图
点击展开查看更多

这相当于:

  1. 你去图书馆找到这本书
  2. 在第10页和第25页分别夹上书签
  3. 告诉别人:“重点内容在这两页书签之间”

结果:没有复印,只是记录了位置信息,零纸张消耗

代码演示

CSHARP
using System;

// 原始字符串
string original = "This is a very long text that needs efficient processing";
Console.WriteLine($"原始字符串: {original}");

// ========== 方式1:传统Substring ==========
string traditionalResult = original.Substring(10, 15);
Console.WriteLine($"Substring结果: '{traditionalResult}'");

// ========== 方式2:使用Span ==========
// 步骤1:将字符串转换为Span(轻量级视图)
ReadOnlySpan<char> textSpan = original.AsSpan();
Console.WriteLine($"创建Span: 零内存分配");

// 步骤2:在Span上切片(还是视图)
ReadOnlySpan<char> slice = textSpan.Slice(10, 15);
Console.WriteLine($"Span切片: 零内存分配");

// 步骤3:只有在需要string时才转换
string spanResult = slice.ToString();  // 这里才分配内存!
Console.WriteLine($"Span转换为字符串: '{spanResult}'");
点击展开查看更多

Span的实际优势场景

场景1:字符串解析

CSHARP
// 解析"192.168.1.1"这样的IP地址
string ipString = "192.168.1.1";

// 传统方式:分割创建多个字符串
string[] parts = ipString.Split('.');
byte ip1 = byte.Parse(parts[0]);
byte ip2 = byte.Parse(parts[1]);
// 创建了4个新字符串:"192", "168", "1", "1"

// Span方式:零内存分配
ReadOnlySpan<char> ipSpan = ipString.AsSpan();
byte ipPart1 = byte.Parse(ipSpan.Slice(0, 3));  // "192"
byte ipPart2 = byte.Parse(ipSpan.Slice(4, 3));  // "168" 
byte ipPart3 = byte.Parse(ipSpan.Slice(8, 1));  // "1"
byte ipPart4 = byte.Parse(ipSpan.Slice(10, 1)); // "1"
// 没有创建任何新字符串!
点击展开查看更多

场景2:高性能搜索

CSHARP
// 在长文本中搜索多个关键词
string longText = "这是一个很长的文本内容...";

ReadOnlySpan<char> textSpan = longText.AsSpan();
var keywords = new[] { "错误", "异常", "警告" };

// 使用Span进行搜索(避免创建子字符串)
foreach (var keyword in keywords)
{
    var keywordSpan = keyword.AsSpan();
    int index = textSpan.IndexOf(keywordSpan);
    if (index != -1)
    {
        // 找到了!可以获取周围上下文,还是零分配
        ReadOnlySpan<char> context = textSpan.Slice(
            Math.Max(0, index - 10), 
            Math.Min(20, textSpan.Length - index + 10)
        );
        Console.WriteLine($"找到 '{keyword}',上下文: {context.ToString()}");
    }
}
点击展开查看更多

完整工具类示例

CSHARP
public static class StringExtensions
{
    /// <summary>
    /// 安全的字符串截取方法
    /// </summary>
    public static string SafeSubstring(this string input, int startIndex, int length = -1)
    {
        if (string.IsNullOrEmpty(input) || startIndex >= input.Length)
            return string.Empty;
            
        if (length == -1)
            return input[startIndex..];
            
        return input.Substring(startIndex, 
               Math.Min(length, input.Length - startIndex));
    }
    
    /// <summary>
    /// 使用Span的高性能截取
    /// </summary>
    public static ReadOnlySpan<char> AsSpanSlice(this string input, int start, int length)
    {
        return input.AsSpan().Slice(start, 
               Math.Min(length, input.Length - start));
    }
    
    /// <summary>
    /// 获取字符串的最后几个字符
    /// </summary>
    public static string TakeLast(this string input, int count)
    {
        if (string.IsNullOrEmpty(input) || count <= 0)
            return string.Empty;
            
        return input[^Math.Min(count, input.Length)..];
    }
}

// 使用示例
string demo = "Hello World";
Console.WriteLine(demo.SafeSubstring(6)); // "World"
Console.WriteLine(demo.TakeLast(3)); // "rld"
点击展开查看更多

版权声明

作者: Donghai

链接: https://mgrowup.com/posts/csharp/substr/

许可证: CC BY-NC-SA 4.0

文章已根据知识共享署名-非商业性使用-相同方式共享4.0国际许可协议授权。请注明来源,仅非商业使用,并保持相同的许可协议。

评论

开始搜索

输入关键词搜索文章内容

↑↓
ESC
⌘K 快捷键