C#读取大文本文件仅第二次触发OutOfMemoryException问题求助
解决大文件提取最后一个文本块的内存溢出问题
我来帮你搞定这个问题~你的核心需求是从不断更新的大文本文件里,提取最后一次出现的、以"My Statistics:"开头的24行文本块,但原代码因为一次性加载整个大文件,导致第二次读取时触发了OutOfMemoryException,这很常见,咱们一步步优化:
原代码的问题根源
File.ReadAllText(@"c:\file.txt")会把整个文件的内容一次性加载到内存里,如果文件非常大,第一次读取就会占用大量内存;30秒后文件更新变大,第二次读取时,之前的大字符串可能还没被垃圾回收,内存直接不够用,就抛出了内存溢出异常。
优化方案:只读取必要的内容
我们不需要加载整个文件,只需要从文件末尾往前找最后一个"My Statistics:",然后读取它后面的24行就行。下面给你两种实现方式:
方式1:倒序遍历行(简单易读)
用File.ReadLines逐行读取文件(不会一次性加载全部内容),然后倒序查找目标头,再提取对应行:
private static string GetLastStatisticsBlock(string filePath) { // 倒序遍历所有行,找到第一个出现的"My Statistics:"(也就是文件里的最后一个) var reversedLines = File.ReadLines(filePath).Reverse().ToList(); int targetHeaderIndex = reversedLines.FindIndex(line => line.StartsWith("My Statistics:")); if (targetHeaderIndex == -1) return string.Empty; // 没找到目标块,返回空 // 从目标头开始取24行,再反转回正序 var targetLines = reversedLines.Skip(targetHeaderIndex).Take(24).Reverse(); return string.Join(Environment.NewLine, targetLines); } // 主循环调用 while (true) { try { string lastBlock = GetLastStatisticsBlock(@"c:\file.txt"); File.WriteAllText(@"last.txt", lastBlock); } catch (Exception ex) { // 加个错误处理,避免循环直接崩溃 Console.WriteLine($"执行出错:{ex.Message}"); } Thread.Sleep(30000); }
方式2:从文件末尾反向读取(超高效,适合超大文件)
如果文件特别大(比如几个GB),上面的方式还是会遍历所有行,我们可以直接定位到文件末尾,往前逐字节读取,找到换行符后拼接行,直到找到目标头并收集够24行:
private static string GetLastStatisticsBlockEfficient(string filePath) { const string TargetHeader = "My Statistics:"; List<string> collectedLines = new List<string>(); using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var reader = new StreamReader(fileStream)) { fileStream.Seek(0, SeekOrigin.End); long currentPosition = fileStream.Position; while (currentPosition > 0) { fileStream.Seek(--currentPosition, SeekOrigin.Begin); char currentChar = (char)fileStream.ReadByte(); // 遇到换行符就读取当前行 if (currentChar == '\n' || currentChar == '\r') { string line = reader.ReadLine(); if (!string.IsNullOrWhiteSpace(line)) { collectedLines.Insert(0, line); // 找到目标头且已经收集够24行,直接停止 if (line.StartsWith(TargetHeader) && collectedLines.Count >= 24) break; } } } } // 提取从目标头开始的24行 int headerIndex = collectedLines.FindIndex(line => line.StartsWith(TargetHeader)); return headerIndex == -1 ? string.Empty : string.Join(Environment.NewLine, collectedLines.Skip(headerIndex).Take(24)); }
额外建议
- 加上错误处理:比如文件被占用、读取失败时,不要让循环直接崩溃,而是记录错误后继续执行
- 如果文件更新频率固定,可以考虑监控文件变化(用
FileSystemWatcher),而不是固定30秒轮询,更高效
内容的提问来源于stack exchange,提问作者Erofh Tor
相关产品推荐
相关产品推荐

