直接用 file.readalltext 读 gb 级文本文件大概率触发 outofmemoryexception;应改用 filestream + 合理缓冲区(ssd 用 16kb、机械盘用 64kb),避免默认 4kb 或盲目设过大,慎用 bufferedstream,逐行读取优先用指定缓冲的 streamreader 并注意编码与内存复用。

直接用 File.ReadAllText 读 GB 级文本文件,大概率触发 OutOfMemoryException;不是代码写错了,是它本就不该干这活。
FileStream + 缓冲区大小怎么设才不翻车
默认 4KB 缓冲对现代 SSD 来说偏小,但盲目拉到 1MB 又会拖慢 GC。实测在连续读取场景下,16KB–64KB 是吞吐与内存开销的合理交界点:
-
FileStream构造时显式传入bufferSize: 65536(64KB),比依赖默认值更可控 - 机械硬盘建议用 64KB,SSD 可降到 16KB(
bufferSize: 16384) - 别设成 256KB 以上——磁盘 IO 已不是瓶颈,多占的内存只增加 GC 压力
- 避免
bufferSize: 0或未指定:.NET 会悄悄重置为 4096,失去调优意义
BufferedStream 到底要不要套一层
绝大多数情况下不用。因为 FileStream 默认已启用缓冲,再包一层 BufferedStream 不仅不提速,还会多一次内存拷贝,甚至干扰异步调度:
- 只有当你拿到的是**无缓冲能力的流**(比如
NetworkStream、或手动创建的FileStream并设了bufferSize: 1)时,BufferedStream才有意义 - 真要包装,必须先禁用
FileStream自身缓冲:new FileStream(path, ..., bufferSize: 1),再传给BufferedStream - 绝对不要嵌套:
new BufferedStream(new BufferedStream(fs))—— 没叠加效果,只加对象开销 -
BufferedStream.ReadAsync内部仍是同步填缓冲,不能替代FileStream.ReadAsync的底层重叠 IO
逐行读大文本文件的正确姿势
用 StreamReader 是最常用也最容易出问题的方式——它默认缓冲 1024 字节,遇到超长行或混合编码会卡住或乱码:
- 显式指定缓冲区大小:
new StreamReader(fs, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 65536) - 避免
ReadLine()处理超长日志行:改用ReadBlock+ 手动切分,防止单行撑爆内存 - 如果文件编码不确定,优先用
StreamReader的detectEncodingFromByteOrderMarks: true,而不是靠猜测 - 注意
StreamReader的Dispose()会连带关闭底层FileStream,若需复用流,构造时传leaveOpen: true
异步读取时容易漏掉的关键点
ReadAsync 看似简单,但线程上下文、缓冲区生命周期和完成语义常被忽略:
- 缓冲区数组(如
byte[] buffer)必须在 async 方法作用域内保持有效——不能是栈分配或短命对象 - 不要在循环里反复
new byte[65536],考虑用ArrayPool<byte>.Shared.Rent(65536)</byte>复用 -
FileStream必须用FileOptions.Asynchronous创建,否则ReadAsync会退化为同步+线程池模拟 -
await fs.ReadAsync(buffer, 0, buffer.Length)返回值是实际读取字节数,永远要检查是否为 0(EOF)或小于预期(部分读)
缓冲区不是越大越好,异步不是加个 async 就完事,而 BufferedStream 更不是性能银弹——它只在特定流缺失缓冲能力时才该出场。真正影响大文件吞吐的,往往是缓冲策略、内存复用和底层 IO 选项的组合,不是某一个类名。










