必须显式指定encoding.utf8并用using包裹streamreader;readline()返回null才表示eof;大文件禁用readtoend();否则易致乱码、内存溢出或句柄泄漏。

不显式指定编码,90% 的中文文件读取会乱码;不用 using 包裹,迟早内存泄漏或文件句柄卡死。
StreamReader 构造时必须传 Encoding.UTF8
Windows 记事本保存的 UTF-8 文件默认无 BOM,StreamReader 的无参构造函数(new StreamReader("file.txt"))会 fallback 到 Encoding.Default(通常是 GBK),导致中文显示为 或其他乱码字符。这不是文件损坏,是解码错位。
- ✅ 正确写法:
new StreamReader("data.txt", Encoding.UTF8) - ❌ 错误写法:
new StreamReader("data.txt")(隐式用Encoding.Default) - ⚠️ 不要用
Encoding.GetEncoding("UTF-8"):拼错字符串会抛ArgumentException,Encoding.UTF8更安全、更轻量 - ? 若需兼容 GB2312/GBK 文件,显式传
Encoding.GetEncoding("GB2312"),但生产环境建议统一转为 UTF-8 with BOM 或明确标注编码
ReadLine() 返回 null 才是 EOF,空行是 ""
新手常把 ReadLine() 返回 null 误解为空行或异常,结果写出无限循环或 NullReferenceException。它只在真正到达文件末尾时返回 null;换行符后无内容的空行,返回的是空字符串 ""。
- ✅ 安全循环写法:
while ((line = reader.ReadLine()) != null)—— 每次赋值+判空一步到位 - ❌ 危险写法:
while (reader.ReadLine() != null)—— 每次调用都吃掉一行,第一行直接丢失 - ? 若需保留原始换行符,别用
ReadLine(),改用Read()或ReadBlock(),但得自己处理缓冲区和编码边界
大文件严禁用 ReadToEnd()
ReadToEnd() 会把整个文件一次性加载进内存,500MB 日志文件可能直接触发 OutOfMemoryException,尤其在容器或 32 位进程中。它适合小配置文件,不适合真实场景下的日志、CSV 或导出数据。
- ✅ 替代方案:
- 仅统计行数:
int count = File.ReadLines("log.txt").Count();(惰性求值,内存恒定) - 逐行处理:
using var r = new StreamReader(...); while ((line = r.ReadLine()) != null) { ... } - 随机访问某几行:
File.ReadLines(path).Skip(1000).Take(10)
- 仅统计行数:
- ❌ 禁止场景:日志分析、ETL 导入、用户上传的任意大小文本文件
- ?
File.ReadAllText()底层也用StreamReader,但它封装了ReadToEnd(),同样不适用于大文件
务必用 using 包裹 StreamReader 实例
StreamReader 持有底层 FileStream 句柄,不释放会导致文件被锁定、句柄泄漏,甚至进程崩溃。.NET 不保证 GC 何时回收,也不能依赖析构函数。
- ✅ 必须写成:
using (var reader = new StreamReader("x.txt", Encoding.UTF8)) { ... } - ❌ 禁止写法:
var reader = new StreamReader(...); reader.Close();(没 catch 异常时,Close()根本不会执行) - ?
using编译后等价于try/finally+Dispose(),是唯一可靠方式 - ⚠️ 注意:
File.OpenText("x.txt")返回的StreamReader同样需要using,它只是语法糖,不是免托管资源的魔法
编码指定、null 判断、内存控制、资源释放——这四点漏掉任何一项,在真实项目里都可能变成半夜告警或用户投诉。尤其 Encoding.UTF8 和 using,不是“可选最佳实践”,是硬性底线。










