StreamReader读UTF-8无BOM文件需显式指定Encoding.UTF8,否则默认Encoding.Default导致中文乱码;ReadLine()返回null表示EOF,空行返回"";大文件禁用ReadToEnd()以防内存溢出。

StreamReader 默认编码可能读错中文,必须显式指定
Windows 记事本保存的 UTF-8 文件(无 BOM)用 StreamReader 直接打开,中文大概率变乱码。这不是文件损坏,而是 StreamReader 构造时默认用 Encoding.Default(通常是 GB2312 或 GBK),而 UTF-8 无 BOM 时无法自动识别。
正确做法是显式传入 Encoding.UTF8:
using (var reader = new StreamReader("data.txt", Encoding.UTF8))
{
string line = reader.ReadLine();
}
- 不要依赖
new StreamReader(string path)的无参重载——它对非 ASCII 文本极不安全 - 若不确定源文件编码,可用
File.ReadAllBytes()+Encoding.DetectEncoding()(需自行实现或引用第三方库)预判,但生产环境更推荐统一约定为 UTF-8 with BOM 或明确标注编码 -
Encoding.UTF8比Encoding.GetEncoding("UTF-8")更轻量,且避免因字符串拼写错误抛出ArgumentException
逐行读取时 ReadLine() 返回 null 表示已到文件末尾
ReadLine() 是最常用方式,但它不抛异常、不返回空字符串来表示结束,而是返回 null。新手常误判为空行或忽略判断,导致无限循环或 NullReferenceException。
典型安全写法:
using (var reader = new StreamReader("log.txt", Encoding.UTF8))
{
string line;
while ((line = reader.ReadLine()) != null)
{
// 处理 line,此时 line 不为 null
}
}
- 不能写成
while (reader.ReadLine() != null)—— 这会跳过第一行,因为每次调用都消耗一行 - 空行(\n 或 \r\n 后无内容)会返回空字符串
"",不是null;只有 EOF 才返回null - 若需保留换行符,改用
Read()或ReadBlock(),但需手动处理字符缓冲和编码边界
大文件别用 ReadToEnd(),内存爆掉比报错还难排查
ReadToEnd() 看似方便,但它会把整个文件一次性加载进托管堆。一个 500MB 的日志文件调用它,很可能触发 OutOfMemoryException,尤其在 32 位进程或内存受限容器中。
替代方案取决于用途:
- 只需统计行数?用
while (reader.ReadLine() != null) count++,内存占用恒定 ~1KB - 需要随机访问某几行?先用
File.ReadLines()(返回IEnumerable<string></string>,惰性求值)配合Skip()/Take(),比StreamReader+ReadLine()更简洁 - 必须全文本处理且确定文件小(ReadToEnd(),并加 try/catch 和长度校验
using 语句没写好,文件句柄泄漏比想象中快
StreamReader 包装了底层 FileStream,未正确释放会导致“文件正被另一个进程使用”类错误,尤其在 Windows 上复用同名文件时。
常见疏漏:
- 写了
try却忘了finally { reader?.Close(); }——using就是为此存在,别手写 - 嵌套流时传入已打开的
FileStream,却在using外提前关闭了它:using (var fs = File.OpenRead("x.txt")) using (var r = new StreamReader(fs)) { ... }是安全的;但若fs.Close()在r之前调用,r后续操作会抛ObjectDisposedException - 异步场景下误用同步
StreamReader:应优先选StreamReader.ReadAsync()配合await,而非在线程池里阻塞调用ReadLine()
文件路径含中文、权限不足、网络驱动器断连这些外部因素,不会因 StreamReader 写得漂亮就消失——但至少让资源泄漏这个可控变量守住。


















