MemoryStream读写字节数组时应避免直接ToArray(),因其返回整个缓冲区含冗余零字节;正确做法是GetBuffer()+Length组合,写完需重置Position=0,大文件应预设容量,跨线程需手动同步。
MemoryStream 读写字节数组时,别直接用 ToArray() 拿数据
很多人一上来就 ms.toarray(),以为拿到的就是当前写入的全部字节——其实它返回的是整个内部缓冲区(含未使用的预留空间),可能带一堆 0x00 尾部垃圾。真正有效的数据长度得看 ms.length,但更安全的是用 ms.getbuffer() + ms.length 组合。
- 写完数据后,
ms.Position可能不在末尾,ToArray()不受位置影响,但会复制整个底层数组 - 如果只读不写,
GetBuffer()返回原始引用,注意别意外修改;需要只读副本就用ToArray()或new byte[ms.Length]+ms.Read() -
MemoryStream构造时传入外部数组(如new MemoryStream(buffer)),默认是可写的,改它会影响原数组——加writeable: false参数才安全
用 MemoryStream 做 JSON / 图片等二进制序列化时,记得重置 Position
写完数据后 Position 停在末尾,直接交给 JsonSerializer.Deserialize() 或 Image.FromStream() 会读不到内容,报 Unexpected end of stream 或空对象。
- 常见错误:写完立刻传给下游 API,没调
ms.Position = 0 - 如果后续还要写,用
ms.Seek(0, SeekOrigin.Begin)更明确 - 某些库(如
System.Text.Json)的DeserializeAsync内部会自动 seek,但多数老式 API(BinaryFormatter、Image、XmlSerializer)不会
大文件场景下,别让 MemoryStream 无限制扩容
MemoryStream 默认按需翻倍扩容,一个 200MB 的流可能实际占用 512MB 内存(因 256MB → 512MB 一次增长),OOM 风险高,且 GC 压力大。
- 预估大小时,用
new MemoryStream(initialCapacity)初始化,比如处理已知 10MB 的 protobuf 数据,就传10 * 1024 * 1024 - 完全不确定大小又怕爆内存?考虑换
FileStream或分块处理,MemoryStream不是万能缓存 - .NET 6+ 支持
MemoryManager<byte>自定义分配器,但绝大多数业务没必要,先从预分配入手
跨线程共享 MemoryStream 必须自己同步
MemoryStream 不是线程安全的——Write() 和 Read() 同时调用可能破坏 Position、覆盖缓冲区,甚至抛 ArgumentOutOfRangeException。
- 没有内置锁,
lock(ms)可行但粒度太粗;更推荐把流封装成单次用途(写完即传参给下游,不再复用) - 如果必须复用,用
ReaderWriterLockSlim区分读写,或改用ConcurrentQueue<byte[]>+ 独立缓冲区组装 - ASP.NET Core 中从
HttpContext.Request.Body拿到的Stream是HttpRequestStream,不是MemoryStream,别想当然加锁
最常被忽略的一点:构造时传入的字节数组是否可变,决定了你是在操作副本还是原始内存——这直接影响到并发行为和 GC 生命周期,不看文档直接传参很容易踩坑。









