MemoryStream 是字节数组的流式包装,非内存文件句柄;用错构造、不重置 Position、误调 ToArray() 会导致空数据或脏字节。

MemoryStream 不是“内存文件句柄”,它只是字节数组的流式包装;用错构造方式、不重置 Position、误调 ToArray(),三分钟就能让下游读到空数据或脏字节。
new MemoryStream() 和 new MemoryStream(byte[]) 的行为差异
默认构造函数 new MemoryStream() 创建的是可读可写、缓冲区动态扩容的流;而 new MemoryStream(byte[]) 默认创建的是可读但不可写的视图——哪怕你传入的是可变数组,Write() 也会直接抛 NotSupportedException。
- 需要后续写入 → 用
new MemoryStream()或new MemoryStream(byte[], writable: true)(.NET Core 2.1+) - 仅封装已有只读数据 →
new MemoryStream(readonlyBytes)更安全,避免意外修改原数组 - 不确定来源时,务必先检查
stream.CanWrite和stream.CanRead,别硬调方法
写完不调 stream.Position = 0 就读,90% 情况下读不到内容
MemoryStream 内部有位置指针,Write() 后指针停在末尾。此时直接交给 StreamReader、JsonSerializer.Deserialize() 或 Image.FromStream(),会立刻报 Unexpected end of stream 或返回空对象——不是 API 有问题,是流没 rewind。
- 写完立即执行
stream.Position = 0或stream.Seek(0, SeekOrigin.Begin) - 某些新 API(如
System.Text.Json的DeserializeAsync)内部会自动 seek,但BinaryFormatter、XmlSerializer、Image等老接口不会 - 如果后续还要继续写,用
Seek()比直接赋值Position更语义清晰
ToArray() vs GetBuffer():哪个返回“真正有效的字节”
ToArray() 返回一个**新分配的、长度精确为 stream.Length 的字节数组**,安全但有拷贝开销;GetBuffer() 返回的是整个底层缓冲区引用(可能含大量 0x00 填充),长度常远大于 Length,直接用会读到脏数据。
- 要导出可靠字节 → 无条件用
stream.ToArray() - 要零拷贝复用缓冲区(比如高频小消息拼接)→ 用
stream.GetBuffer()+stream.Length截取,且确保不会被 GC 回收(如配合ArrayPool).Shared - 用
GetBuffer()后又写入,可能污染原数组——只读场景才考虑它
大文件或高吞吐场景下,MemoryStream 容易爆内存
默认扩容策略是翻倍增长:256MB → 512MB 一次就吃掉双倍托管堆,GC 压力陡增,OutOfMemoryException 风险极高。它不适合缓存 GB 级响应体或长期持有引用。
- 预估大小时,用
new MemoryStream(initialCapacity)初始化,比如处理 8MB Protobuf 数据,就传8 * 1024 * 1024 - 完全不确定大小又怕 OOM?换
FileStream临时落盘,或改用分块流式处理(如HttpContent.ReadAsStreamAsync()直接管道传递) - ASP.NET Core 中别把
MemoryStream存进静态集合或缓存服务——它不持久、不线程安全、不释放内存
最容易被忽略的点:跨线程共享 MemoryStream 时,它本身没有锁机制,Read() 和 Write() 并发调用会导致 Position 错乱、缓冲区覆盖甚至 ArgumentOutOfRangeException;不是加个 lock 就万事大吉,而是该设计成单次写入、单次消费的短生命周期对象。










