C# ReadOnlySpan
最后修改日期:2025 年 4 月 27 日
本教程讲解如何在 C# 中使用 ReadOnlySpan 结构体来高效地处理内存。ReadOnlySpan 提供了一种安全的方式来处理内存切片而无需分配内存。
ReadOnlySpan 结构体表示一个连续的内存区域,是 .NET 中的一个关键特性,它允许高效地处理数据而无需创建不必要的副本。通过提供数组、字符串或原始内存的轻量级、不可变视图,ReadOnlySpan 优化了性能并降低了内存开销。它的设计确保任何修改都被禁止,这使其成为数据完整性和不变性至关重要的场景中的绝佳工具。此外,ReadOnlySpan 是在栈上分配的,有助于降低垃圾回收的压力并提高整体应用程序性能。
ReadOnlySpan 特别适合解析结构化数据、处理子字符串或处理数据缓冲区等任务,尤其是在高性能应用程序中。它的类型安全性确保开发人员可以自信地使用强类型数据,而其内置的边界检查可防止越界访问,从而降低运行时错误的概率。这些特性使 ReadOnlySpan 成为处理内存密集型操作中连续数据的强大抽象,在这些操作中,可靠性和效率都至关重要。
基本的 ReadOnlySpan 示例
此示例展示了如何从数组创建 ReadOnlySpan 并访问其元素而无需复制数据。
int[] numbers = { 1, 2, 3, 4, 5 };
ReadOnlySpan<int> span = numbers.AsReadOnlySpan();
Console.WriteLine($"First: {span[0]}");
Console.WriteLine($"Length: {span.Length}");
Console.WriteLine($"Slice (2-4): {string.Join(", ", span.Slice(2, 2).ToArray())}");
该程序从一个整数数组创建一个 ReadOnlySpan,并演示了如何访问元素和切片。AsReadOnlySpan 创建一个跨度,而不复制数组。该示例使用索引访问第一个元素,检索跨度的长度,并创建一个从索引 2 开始的包含两个元素的切片。
由于 ReadOnlySpan 是不可变的,因此它可以确保底层数据无法被修改,从而在避免内存分配的同时保持性能。
使用 ReadOnlySpan 切割字符串
ReadOnlySpan 对于高效的字符串操作非常有用。此示例解析一个字符串,而不创建子字符串。
string text = "Hello, World!";
ReadOnlySpan<char> span = text.AsSpan();
ReadOnlySpan<char> hello = span.Slice(0, 5);
ReadOnlySpan<char> world = span.Slice(7, 5);
Console.WriteLine($"First word: {hello.ToString()}");
Console.WriteLine($"Second word: {world.ToString()}");
该示例从一个字符串创建一个 ReadOnlySpan,并使用切片提取子字符串。AsSpan 在字符串的字符上创建一个跨度,而不复制字符串。该程序通过指定起始索引和长度来切割跨度以提取“Hello”和“World”。
与传统的子字符串方法不同,使用 ReadOnlySpan 进行切片可以避免分配新字符串,从而提高性能。ToString 方法将跨度转换回字符串以进行显示,但切片本身是无分配的。
使用 ReadOnlySpan 解析数字
ReadOnlySpan 可以高效地解析数据。此示例从逗号分隔的字符串中解析整数。
using System;
string data = "42,100,7";
ReadOnlySpan<char> span = data.AsSpan();
List<int> numbers = [];
while (!span.IsEmpty)
{
int commaIndex = span.IndexOf(',');
ReadOnlySpan<char> numberSpan = commaIndex == -1 ? span : span.Slice(0, commaIndex);
numbers.Add(int.Parse(numberSpan));
span = commaIndex == -1 ? [] : span.Slice(commaIndex + 1);
}
Console.WriteLine($"Parsed numbers: {string.Join(", ", numbers)}");
该程序使用 ReadOnlySpan 从字符串中解析整数,以避免分配内存。它从输入字符串创建一个跨度,并迭代查找逗号以分割数据。对于每个段,IndexOf 找到下一个逗号,Slice 提取数字部分。int.Parse 将跨度转换为整数,并将其添加到列表中。
跨度被更新以跳过逗号并继续解析。这种方法是高效的,因为它避免了创建中间字符串,而是依赖于 ReadOnlySpan 直接使用字符串内存的能力。
使用内存缓冲区
ReadOnlySpan 可以处理原始内存缓冲区。此示例将字节缓冲区作为跨度进行处理。
byte[] buffer = { 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 };
ReadOnlySpan span = buffer;
int firstInt = BitConverter.ToInt32(span.Slice(0, 4));
int secondInt = BitConverter.ToInt32(span.Slice(4, 4));
Console.WriteLine($"First int: {firstInt}");
Console.WriteLine($"Second int: {secondInt}");
该示例将字节数组视为 ReadOnlySpan 以从缓冲区读取整数。该数组包含两个小端格式的 32 位整数。Slice 提取 4 字节段,BitConverter.ToInt32 将每个段转换为整数。使用 ReadOnlySpan 避免了复制缓冲区,并提供了一种安全、边界检查的方式来访问数据。此技术对于处理二进制数据(例如网络数据包或文件格式)非常有用,在这些数据中,需要直接内存访问而无需分配内存。
将 ReadOnlySpan 与栈分配一起使用
ReadOnlySpan 可以与栈分配的内存一起使用,以实现最大效率。此示例使用 stackalloc。
using System;
Span<int> temp = stackalloc int[3];
temp[0] = 10; temp[1] = 20; temp[2] = 30;
ReadOnlySpan<int> span = temp;
Console.WriteLine($"Sum: {span[0] + span[1] + span[2]}");
Console.WriteLine($"Slice (1-2): {string.Join(", ", span.Slice(1, 2).ToArray())}");
该程序使用 stackalloc 在栈上分配一个临时数组,该数组被转换为 ReadOnlySpan。 Span<int> 填充了值,然后转换为 ReadOnlySpan 以进行安全、只读访问。该示例计算元素的总和并创建一个切片。栈分配避免了堆分配,使这种方法对于小型、短期的缓冲区非常有效。
ReadOnlySpan 确保通过边界检查安全地访问数据,并且其不变性可防止对底层内存的意外修改。
使用 ReadOnlySpan 比较字符串
ReadOnlySpan 支持高效的字符串比较。此示例比较字符串的部分内容。
using System;
string text1 = "Hello, World!";
string text2 = "Hello, Universe!";
ReadOnlySpan<char> span1 = text1.AsSpan(0, 5);
ReadOnlySpan<char> span2 = text2.AsSpan(0, 5);
bool areEqual = span1.SequenceEqual(span2);
Console.WriteLine($"First 5 chars equal: {areEqual}");
该示例使用 ReadOnlySpan 比较两个字符串的前五个字符。AsSpan 在字符串的相关部分上创建跨度,SequenceEqual 检查它们是否相同。
此方法比创建子字符串或使用传统的字符串比较方法更有效,因为它直接在字符串的内存上操作而无需分配内存。 此技术在性能关键型场景(例如解析或验证大型数据集)中特别有用,在这些场景中,最大限度地减少内存使用和处理时间至关重要。
带有 ReadOnlyMemory 的 ReadOnlySpan
ReadOnlySpan 可以与 ReadOnlyMemory 一起使用以实现灵活的内存管理。此示例演示了转换。
using System;
int[] numbers = { 1, 2, 3, 4, 5 };
ReadOnlyMemory<int> memory = numbers.AsMemory();
ReadOnlySpan<int> span = memory.Span;
Console.WriteLine($"First: {span[0]}");
Console.WriteLine($"Last: {span[^1]}");
该程序将数组转换为 ReadOnlyMemory,然后从中提取 ReadOnlySpan。 AsMemory 创建一个内存对象,并且 Span 属性提供了一个跨度以进行直接访问。该示例使用索引访问第一个和最后一个元素。 ReadOnlyMemory 对于在方法或异步操作之间传递内存区域非常有用,而 ReadOnlySpan 提供了一个轻量级的、基于栈的视图以进行立即处理。 这种组合允许灵活而高效的内存管理,尤其是在涉及异步或分层数据处理的场景中。
来源
本教程介绍了如何在 C# 中使用 ReadOnlySpan 进行高效的内存处理,包括数组、字符串、缓冲区和栈分配的内存。
作者
列出所有 C# 教程。