经典之选:String.Substring方法
Substring 是大多数 C# 开发者接触的第一个字符串截取方法,它简单直接
基本用法
string text = "0123456789";
// 从指定位置截取到末位
string part1 = text.Substring(7); // "789"
// 从指定位置截取指定长度,从0开始,数5个字符
string part2 = text.Substring(0, 5); // "01234"需要注意边界检查,如果过界的话会提示异常:ArgumentOutOfRangeException
// 这些调用都会抛出异常
// 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
基本使用
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类型主要优势在于从后往前计数的便利性
基本使用
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

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

基本使用
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"Substring 和 Span 的性能对比
// 处理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页的内容(原始字符串)。
string books = "This is a very long text that needs efficient processing";
// 想象books就是《代码大全》,我们想阅读"very long text"这部分使用传统的方式阅读第10页到第24页的内容(Substring)
// 传统方式 - 创建新字符串(有内存分配)
string substring = longText.Substring(10, 15); // "very long text"这相当于:
- 你去图书馆找到这本书
- 把第10页到第24页的内容复印下来
- 带着复印件回家
结果:消耗了纸张(内存),创建了完整的副本
Span方式:书签定位(零内存分配)
// 使用Span - 零内存分配!
ReadOnlySpan<char> textSpan = longText.AsSpan();
ReadOnlySpan<char> slice = textSpan.Slice(10, 15); // 只是视图这相当于:
- 你去图书馆找到这本书
- 在第10页和第25页分别夹上书签
- 告诉别人:“重点内容在这两页书签之间”
结果:没有复印,只是记录了位置信息,零纸张消耗
代码演示
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:字符串解析
// 解析"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:高性能搜索
// 在长文本中搜索多个关键词
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()}");
}
}完整工具类示例
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"
评论