streamreader.readline()是大文件逐行读取的稳态选择,需显式指定编码、using包裹、复用实例;file.readlines()仅适合简单遍历;随机访问须预建行偏移索引;极致性能可选span手动解析。

直接读大文本文件,File.ReadAllLines() 会爆内存,StreamReader.ReadLine() 是唯一稳得住的选择;真要兼顾随机访问和速度,得自己建行偏移索引。
StreamReader.ReadLine() 是大文件逐行读取的默认起点
它内部用缓冲区(默认 1024 字节)流式解码,不把整文件拖进内存,稳定占用几 KB 到几十 KB。别图省事写 new StreamReader(path) —— 缺少编码参数容易乱码或触发 BOM 检测开销。
- 必须显式指定编码:
new StreamReader(path, Encoding.UTF8)或Encoding.GetEncoding("GB2312"),尤其部署到 Linux 服务器时,Encoding.Default很可能不是你想要的 - 用
using包裹,确保Dispose()被调用,否则文件句柄泄漏;别在循环里反复 new 实例,一个复用到底 -
reader.ReadLine()返回null表示 EOF,空行返回空字符串"",不是null - 如果文件含损坏字节或混合编码,会抛
ArgumentException;可设detectEncodingFromByteOrderMarks: false或捕获异常降级处理
File.ReadLines() 看似简洁,但有隐藏约束
它返回 IEnumerable<string></string>,底层也是基于 StreamReader 做延迟加载,语法干净,适合简单遍历。但它不是“万能替代”——不能重复遍历,且异常时机靠前:文件被占用、权限不足、路径不存在,都在第一次 foreach 迭代时就炸,而不是等读到末尾。
- 不能多次
foreach同一个File.ReadLines()结果,除非先.ToList()或.ToArray(),但那就失去流式意义了 - 不支持跳过前 N 行后再继续,也不支持中途 Seek;想跳过 100 行?只能
for丢弃,别指望DiscardBufferedData()起作用 - 它自动识别 BOM,对无 BOM 的 UTF-8 文件仍会按系统默认编码解析,所以乱码问题照样发生
需要随机访问某一行?先建行偏移索引
StreamReader 不支持 Seek 到第 N 行,因为行长度不固定。硬扫 100 万行耗时不可控,必须预处理建索引。
- 第一遍扫描只记每行起始位置:
stream.Position,写入轻量二进制或 CSV(每行一个long),索引文件本身几 MB,远小于原始文件 - 后续读取时用
FileStream+Seek()跳转,再用StreamReader读该行:fs.Seek(offsets[lineNumber], SeekOrigin.Begin) - 加
FileOptions.SequentialScan提示 OS 使用顺序读优化,对 GB 级文件有效 - 注意跨缓冲区换行符边界:若 \n 恰好在 buffer 末尾,需保留未完成行头,避免下一次读错位
追求极致性能?考虑 Span 手动解析
绕过字符串解码、避免 GC 分配,纯 ASCII 或简单 CSV 场景下比 StreamReader 快 2–5 倍。但代价是代码复杂、易出错,且只适用于格式可控的文件。
- 用
FileStream.ReadAsync(Memory<byte>)</byte>配合栈上Span<byte></byte>处理缓冲区 - 用
IndexOf((byte)'\n')找行尾,Utf8Decoder.Decode()按需转目标行字符串 - 必须处理跨 buffer 的换行符,即 \n 在当前 buffer 末尾时,要把剩余部分暂存,拼到下一块 buffer 开头
- 别盲目上手——90% 的业务场景,
StreamReader+ 合理缓冲区(如bufferSize: 1024 * 1024)已足够
真正难的不是“怎么读”,而是判断哪一行值得花力气建索引、哪一类乱码该提前拦截、以及什么时候该放弃“逐行”思路改用分块+并行处理——这些边界往往在压测后才浮现。











