Span<byte>、ReadOnlySpan<byte> 和 byte[] 的区别

这是一个非常关键的问题,掌握好 Span<byte>ReadOnlySpan<byte>byte[] 的区别,对我们优化程序的性能非常重要。

下面从定义、差异、使用场景、注意点逐一解释:


✅ 一、概念对比

类型 描述 是否可修改 是否分配堆内存
byte[] 实际存储数据的托管数组 ✅ 是 ✅ 是(会触发 GC)
Span<byte> 指向连续内存的“可写切片视图”,不分配内存 ✅ 是 ❌ 否(栈或固定内存)
ReadOnlySpan<byte> Span 的只读版本,不能修改数据 ❌ 否 ❌ 否(安全只读)

✅ 二、用途场景

场景 推荐使用
原始数据接收/缓冲区 byte[](原始载体)
频繁读取和处理、切片解析 Span<byte>(高性能,零分配)
只读解析、不需要写入 ReadOnlySpan<byte>(安全性更好)
与老 API 或网络 IO 打交道 byte[](因为 Span<T> 不能跨线程/不能 async 保存)

✅ 三、典型使用示例

byte[] 作为原始数据载体

byte[] raw = new byte[1024];
raw[0] = 0x01;

✅ 使用 Span<byte> 提高解析性能(零拷贝)

Span<byte> span = raw.AsSpan(offset, length);
short value = BitConverter.ToInt16(span); // 从切片中读取

✅ 使用 ReadOnlySpan<byte> 提供安全只读接口

public void Parse(ReadOnlySpan<byte> data)
{
    int id = BitConverter.ToInt32(data.Slice(0, 4));
}

⚠️ 四、使用时注意事项

注意点 Span / ReadOnlySpan 的说明
❗ 不能存到字段或类属性中(生命周期限制) Span<T> 是栈上类型,只能用在方法内部
❗ 不能跨 async/await 使用 因为 Span 是栈分配类型,不能“悬空”传出去
❗ 不能传给 Task.Run(() => ...) 里的代码 会造成潜在数据悬空风险
✅ 用 Slice() 替代 SubArray() 不分配新数组,避免 GC
✅ 如果要传给只能接受 byte[] 的 API,才调用 .ToArray()

✅ 总结建议

如果我们想... 用这个类型
存数据、序列化、传输 byte[]
解析数据、提取子段、性能优先 Span<byte>
保证不被篡改的数据视图 ReadOnlySpan<byte>

💡 提示

C# 的 Span<T> 系统是为了高性能低内存分配设计的。我们采集的是高频数据,我们做数据解析时越多用 Span/ReadOnlySpan,GC 压力就越小、CPU 越轻松

posted @ 2025-05-29 11:07  青云Zeo  阅读(107)  评论(0)    收藏  举报