我们都知道 使用数组的时候 就是要在堆区申请一块内存 而不用了这块内存 GC会自动回收 对于业务逻辑代码 一般不用吝啬这点性能 但是对于要频繁(数十万次及以上 每秒)申请的时候 还是会有一些的影响的
今天来学一下 Span 这个可以在堆区申请空间
stackalloc
首先讲一下 stackalloc
看到这个会不会想起 C语言的 malloc
这个是c#在栈区申请一块儿空间 但是由于是指针 所有要在 safe 语句块中
unsafe {
int* array = stackalloc int[5]; // 在栈上分配 5 个 int
for (int i = 0; i < 5; i++) {
array[i] = i * i;
}
}
Span
Span<T>
是 C# 7.2 引入的一种 安全的、栈上/堆上通用的切片结构,属于ref struct
,只能在栈上使用。
它的作用:
- 作为一个“视图”,可以安全地引用一段 连续内存(不管是数组、指针、
stackalloc
内存、还是非托管内存)。 - 避免了复制数组,也避免了使用裸指针带来的不安全。
- 提供了切片操作(
Slice
),可以只看内存的一部分。
Span<int> span = stackalloc int[5];
for (int i = 0; i < span.Length; i++)
{
span[i] = i * 3;
}
foreach (var item in span)
{
Console.WriteLine(item);
}
常用方法
using System;
class Program
{
static void Main()
{
// ========== 1. 创建 ==========
int[] arr = { 1, 2, 3, 4, 5 };
Span<int> span = arr; // 从数组
Span<int> stackSpan = stackalloc int[5]; // 栈上分配
stackSpan.Fill(7); // 填充 7
Console.WriteLine("原始 span: " + string.Join(",", span.ToArray()));
Console.WriteLine("stackalloc span: " + string.Join(",", stackSpan.ToArray()));
// ========== 2. 属性 ==========
Console.WriteLine($"Length={span.Length}, IsEmpty={span.IsEmpty}, 第3个={span[2]}");
// ========== 3. 切片 ==========
var sub1 = span.Slice(1, 3); // [2,3,4]
var sub2 = span[2..]; // 从第3个开始
Console.WriteLine("Slice(1,3): " + string.Join(",", sub1.ToArray()));
Console.WriteLine("span[2..]: " + string.Join(",", sub2.ToArray()));
// ========== 4. 复制 ==========
Span<int> target = new int[5];
span.CopyTo(target); // 复制数据
Console.WriteLine("复制后的 target: " + string.Join(",", target.ToArray()));
Span<int> smallTarget = new int[2];
bool ok = span.TryCopyTo(smallTarget); // 尝试复制
Console.WriteLine($"TryCopyTo 结果: {ok}");
// ========== 5. 搜索 ==========
Console.WriteLine($"IndexOf(3)={span.IndexOf(3)}");
Console.WriteLine($"LastIndexOf(3)={span.LastIndexOf(3)}");
Console.WriteLine($"Contains(5)={span.Contains(5)}");
// ========== 6. 转换 ==========
int[] arr2 = span.ToArray(); // 转换为数组
ReadOnlySpan<int> roSpan = span; // 只读视图
Console.WriteLine("ToArray: " + string.Join(",", arr2));
Console.WriteLine("只读 span: " + roSpan[0]);
// ========== 7. 比较 ==========
Span<int> span2 = new int[] { 1, 2, 3, 4, 5 };
Console.WriteLine("SequenceEqual=" + span.SequenceEqual(span2));
// ========== 8. 填充 ==========
span.Fill(9);
Console.WriteLine("Fill(9): " + string.Join(",", span.ToArray()));
// 注意:Span 是 ref struct,不能存到字段/类里,也不能 async/await 捕获
}
}
优势
- 性能好:栈内存分配速度快,而且自动释放,无需 GC。
- 安全性:
Span<T>
避免了指针操作,防止越界访问。 - 零拷贝:通过
Span<T>
可以直接“视图化”已有数据,而不是复制数据。
限制
Span<T>
是ref struct
,不能装箱、不能捕获到lambda
/async
、不能放到类字段里。stackalloc
分配的大小必须是编译期常量,或者运行时传入的正整数,但不能太大(栈溢出风险)。- 不能把
Span<T>
返回到方法外(因为可能引用的是栈上的数据)。
使用场景
- 临时缓冲区:如处理字符串拼接、数据序列化、网络协议解析。
- 高性能代码:避免频繁分配和 GC。
- 二进制操作:解析字节流、协议栈、内存映射文件。
本文由 jxxxy 创作,采用 知识共享署名4.0 国际许可协议进行许可。
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。