MemoryStream 读写前必须明确是否可读/可写
默认构造的
MemoryStream是可读可写的,但用字节数组构造时(
new MemoryStream(byte[]))会变成只读——哪怕数组本身可变。这是最常踩的坑:调用
Write()或
SetLength()直接抛
NotSupportedException。
实操建议:
需要写入 → 用无参构造或传入可修改的byte[]并设
writable: true(.NET Core 2.1+ 支持) 仅读取已有数据 → 用
new MemoryStream(readonlyArray)更安全 不确定来源时,先检查
CanWrite和
CanRead属性,别硬调
写入后记得 Seek(0, SeekOrigin.Begin) 才能重新读
MemoryStream内部有位置指针(
Position),写完数据后指针停在末尾。此时直接
Read()会返回 0 字节——不是空,是“从末尾开始读”。
常见错误现象:序列化对象到
MemoryStream,不重置位置就交给
StreamReader或反序列化器,结果读不到内容。
实操建议:
写完立即调用stream.Position = 0或
stream.Seek(0, SeekOrigin.Begin)如果后续只读不写,可用
stream.GetBuffer()获取底层数组(注意:可能含未使用填充字节,得配合
stream.Length截取) 更推荐
stream.ToArray()——它返回精确长度的新数组,安全但有拷贝开销
Dispose() 不释放内存,但不调可能泄漏非托管资源
MemoryStream没有非托管句柄,所以不
Dispose()不会导致内存泄漏。但它继承自
Stream,而某些上层 API(如
HttpClient.SendAsync()的
HttpContent)会强制调用
Dispose()。如果子类被重写且依赖
Dispose()清理,跳过就会出问题。
实操建议:
始终用using块包裹,符合 .NET 资源管理约定 不要手动调
GC.Collect()强制回收——
MemoryStream占用的是托管堆,GC 自会处理 大内存流(百 MB 级)可考虑
ArrayPool<byte>.Shared.Rent()</byte>配合自定义流,避免频繁 GC 压力
和 FileStream、NetworkStream 的行为差异点
MemoryStream是纯内存操作,没有 I/O 延迟,但也不支持异步等待(
ReadAsync/
WriteAsync在它内部只是同步调用的包装)。这点容易误导——以为加
await就能提升性能,实际没意义。
实操建议:
别对MemoryStream做
await stream.ReadAsync(...),直接用同步方法更清晰 若需统一处理多种流类型(比如封装通用序列化方法),应判断
stream is MemoryStream分支走同步逻辑 跨线程共享
MemoryStream时,它本身不线程安全——读写不能并发,需自行加锁或用
ConcurrentQueue<byte></byte>等替代方案 真正容易被忽略的是:写入后不重置位置导致读不到数据,以及用只读数组构造却尝试写入。这两个问题在调试时往往表现为“逻辑没错但结果为空”,而不是抛异常。
