C#内存流MemoryStream用法 C#如何读写内存流

来源:这里教程网 时间:2026-02-21 17:41:12 作者:

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>
等替代方案
真正容易被忽略的是:写入后不重置位置导致读不到数据,以及用只读数组构造却尝试写入。这两个问题在调试时往往表现为“逻辑没错但结果为空”,而不是抛异常。

相关推荐