C#读取大文本文件 C#如何高效读取GB级别的文件

1次阅读

应使用 streamreader 按行流式读取 gb 级文件,避免 file.readalllines() 导致 oom;需指定编码、复用实例、预建行偏移索引支持随机访问;对高性能场景可选 span 手动解析,但需谨慎处理边界与编码。

C#读取大文本文件 C#如何高效读取GB级别的文件

StreamReader 按行流式读取,别加载进内存

GB 级文件直接 File.ReadAllLines()File.ReadAllText() 会瞬间 OOM——字符串对象在 .NET 中是 UTF-16 编码,1GB 的 ASCII 文件加载后实际占用约 2GB 内存,还附带 GC 压力。StreamReader 是唯一靠谱的起点,它内部使用缓冲区(默认 1024 字节),逐块解码、按需吐出字符串,内存占用稳定在几 KB 到几十 KB。

实操建议:

  • 始终指定编码,如 new StreamReader(path, Encoding.UTF8),避免 bom 判断开销或乱码
  • ReadLine() 而非 ReadToEnd();若需跳过前 N 行,用循环 + DiscardBufferedData() 无意义,直接 for 丢弃即可
  • 不要在循环里反复新建 StreamReader;一个实例复用到底

需要随机访问某一行?别硬扛,先建索引

StreamReader 不支持 Seek 到第 N 行——因为行长度不固定,无法 O(1) 定位。想“读第 100 万行”,只能从头扫,耗时不可控。真有此需求,必须预处理建行偏移索引。

做法很简单:第一遍扫描只记每行起始 Stream.position,写入一个轻量二进制或 CSV 文件(如每行存一个 long);后续读取时用 FileStream.Seek() 跳转,再用 StreamReader 读该行:

using var fs = new FileStream("file.txt", FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan); fs.Seek(offsets[lineNumber], SeekOrigin.Begin); using var sr = new StreamReader(fs, Encoding.UTF8); string line = sr.ReadLine(); // 就是你要的那行

注意:FileOptions.SequentialScan 提示 OS 使用顺序读优化,对大文件有效;索引文件本身只有几 MB,远小于原始文件。

比逐行更快?试试 Span<byte></byte> + 手动解析

如果文件格式简单(如纯 ASCII 日志、CSV 无引号嵌套),且你愿意放弃部分可读性换性能,Span<byte></byte> 直接操作字节比 StreamReader 快 2–5 倍。核心是绕过字符串解码、避免 GC 分配。

关键点:

  • FileStream.ReadAsync(Memory<byte>)</byte> 配合Span<byte></byte> 处理缓冲区
  • IndexOf((byte)'n') 找行尾,Utf8Decoder.Decode() 按需转字符串(仅对目标行)
  • 务必处理跨缓冲区的换行符(即 n 恰好在 buffer 边界),需保留末尾不完整行头
  • 不推荐新手直接上手——调试困难,编码逻辑(如 UTF-8 多字节)易出错

别忽略文件系统和硬件层的影响

代码再优,遇到机械硬盘、网络共享盘(SMB/NFS)、或 NTFS 压缩属性,吞吐量可能跌到 10MB/s 以下。这些不是 C# 能解决的:

  • 确认文件是否启用了 NTFS 压缩:fsutil behavior query disablelastaccesscompact /q file.txt 查看;压缩文件会强制解压到内存再读,彻底废掉流式优势
  • SSD 上开启 FileOptions.RandomAccess 可能反而拖慢;顺序大文件一律用 SequentialScan
  • 远程文件优先考虑下载本地再处理;SMB 共享下 StreamReader 的缓冲区大小建议调大到 64KB 或 128KB 减少往返

真正卡住的时候,先用 Process Monitor 看是不是在等磁盘 IO,而不是急着改 C# 代码。

text=ZqhQzanResources