读取大型文本文件,内存溢出异常

17
我想读一个大小为500MB的大型TXT文件,首先我使用了

var file = new StreamReader(_filePath).ReadToEnd();  
var lines = file.Split(new[] { '\n' });

但是当我尝试一次性读取所有行时,它会抛出内存溢出异常,然后我尝试逐行读取,但是在读取大约150万行后又抛出内存溢出异常。

  using (StreamReader r = new StreamReader(_filePath))
         {            
             while ((line = r.ReadLine()) != null)            
                 _lines.Add(line);            
         }

或者我使用了

  foreach (var l in File.ReadLines(_filePath))
            {
                _lines.Add(l);
            }

但是我再次收到了以下异常:

类型为'System.OutOfMemoryException'的异常在mscorlib.dll中出现,但未在用户代码中处理。

我的电脑配置很强大,有8GB的内存,所以不应该是我的电脑的问题。

p.s:我尝试在NotePad ++中打开此文件,并收到了“文件太大无法打开”的异常。


2
什么是问题?你只是在描述事物。 - Alvin Wong
1
将所有内容存储在集合中的意义是什么? - CyberDude
1
你提到了“500行”,但是文件的大小是多少,以字节和字符为单位呢?80个字符的500行不应该有问题——每行10亿个字符的500行显然会有问题。 - Jon Skeet
1
@Behnam - 8GB内存中放不下500GB的文件。你需要找到其他处理该文件的方式,而不是要求将整个文件都放入内存中。可以逐行(或者每次处理一小部分行)进行处理。 - Hans Kesting
1
你可以通过不要将整个文件加载到内存中来解决这个问题。显然,你的设计不适合应用程序的需求。你最终会对那些数据做什么?任何处理、过滤等操作吗?也许你需要先将它存储在数据库中。 - CyberDude
显示剩余4条评论
6个回答

39

只需使用File.ReadLines,它会返回一个IEnumerable<string>,并且不会一次性将所有行加载到内存中。

foreach (var line in File.ReadLines(_filePath))
{
    //Don't put "line" into a list or collection.
    //Just make your processing on it.
}

即使只使用空的 foreach 循环 (foreach (var line in File.ReadLines(_filePath)) { }), 问题仍然存在。 - Ben
@Behnam 你确定这个错误不是来自你程序的其他部分吗?尝试在空白解决方案中操作。 - L.B
我刚刚创建了一个控制台应用程序,它只有一行代码 foreach (var line in File.ReadLines(_filePath)) { },但它又出现了异常。 - Ben
2
@Behnam,我刚刚测试了一个大小为8.7GB的文本文件(120,000,000行),运行良好。 - L.B

4
异常的原因似乎是增长的_lines集合但未读取大文件。您正在读取行并将其添加到某个集合_lines中,该集合将占用内存并导致内存不足异常。您可以应用过滤器仅将所需行放入_lines集合中。

1
我刚刚删除了将数据添加到_lines集合的代码行,但问题仍然存在。 - Ben
1
现在它抛出了什么异常? - Adil

3

我知道这是一篇旧帖子,但Google在2021年将我送到了这里。

强调igrimpe上面的评论:

最近我遇到了一个OutOfMemoryException,在循环遍历大型文本文件夹时使用StreamReader.ReadLine()。

正如igrimpe所提到的那样,您有时可能会遇到输入文件中断行不统一的情况。如果您正在循环遍历文本文件并遇到此问题,请仔细检查您的输入文件是否存在意外字符/ ASCII编码的十六进制或二进制字符串等。

在我的情况下,我将60GB的问题文件划分为256MB的块,将我的文件迭代器作为异常捕获的一部分存储问题文本文件,然后通过删除问题行来纠正问题文本文件。


1

编辑:

将整个文件加载到内存中会导致对象增长,如果.NET无法为对象分配足够的连续内存,则会抛出OOM异常。

答案仍然是一样的,您需要流式传输文件,而不是读取全部内容。这可能需要重新设计应用程序,但使用 IEnumerable<> 方法,您可以在应用程序的不同区域堆叠业务流程并推迟处理。


一台具有8GB RAM的“强大”计算机无法将500GB文件存储在内存中,因为500比8还要大。(此外,您不能获得8个字节,因为操作系统将占用一些空间。您无法在.Net中分配所有内存,32位的极限是2GB,打开文件并存储行将使数据重复两次,存在对象大小开销....)
您无法将整个文件加载到内存中进行处理,必须通过处理流文件。

在我的第二种尝试中,我尝试使用StreamReader,即使删除了“_lines.Add(line);”这一行,我仍然收到OutOfMemoryException异常。所以我并不清楚你所说的流式处理是什么意思。 - Ben
也许“行”终止符不是它应该的?如果行没有以\r和\n结尾,内部函数可能仍会将整个文件读入内存,不是吗? - igrimpe
我不确定为什么您在第二个代码片段中没有调用“_lines.Add(line)”时会收到错误,也许您在其他地方有问题?行终止符可能与问题无关 - 除非您运行64位并且拥有大量内存,否则在任何情况下都很难获得500MB的连续内存。 - cjk
测试行终止符是否是问题应该很容易。使用单个方法file.readline(path)创建控制台应用程序。如果它仍然抛出异常,那么单个“行”太长了。最有可能的原因是内部使用了stringbuilder,它必须永久增加其内部数组(即为新的分配空间),而不给GC时间来清理。 - igrimpe
@igrimpe 好主意 - cjk

0

首先你需要计算行数。虽然速度较慢,但你可以读取多达 2,147,483,647 行。

int intNoOfLines = 0;
using (StreamReader oReader = new 
StreamReader(MyFilePath))
{
    while (oReader.ReadLine() != null) intNoOfLines++;
}
string[] strArrLines = new string[intNoOfLines];
int intIndex = 0;
using (StreamReader oReader = new 
StreamReader(MyFilePath))
{
    string strLine;
    while ((strLine = oReader.ReadLine()) != null)
    {
       strArrLines[intIndex++] = strLine;
    }
}

0

对于其他遇到这个问题的人:

如果你在使用StreamReader.ReadLine()时遇到内存不足的情况,我敢打赌你的文件一开始就没有多行。你只是假设它有多行。这是一个容易犯的错误,因为你不能用记事本打开一个10GB的文件。

有一次,我从客户那里收到了一个10GB的文件,本应该是一个数字列表,但他使用逗号作为分隔符,导致整个文件都是一行,这显然会导致ReadLine()崩溃。

尝试使用StreamReader.Read()从流中读取几千个字符,并查找'\n'。很可能你找不到。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接