将大文本文件加载到字符串中

Load a large text file into a string

我想将一个 150 MB 的文本文件加载到一个字符串中。该文件是 UTF16 编码的,因此它会在内存中生成一个大约 150 MB 的字符串。我尝试过的所有方法都会导致内存不足异常。

我知道这是一个巨大的字符串,当然不是我想做的事情。但是,如果不对即将推出的应用程序进行大量真正深入的更改,目前我真的无能为力。该文件中没有均匀分布的行集。一行可以包含整个文件大小的 80% 左右。

这是我尝试过的方法:

方法一

// Both of these throw Out of Memory exception
var s = File.ReadAllText(path)
var s = File.ReadAllText(path, Encoding.Unicode);

方法二

var sb = new StringBuilder();

// I've also tried a few other iterations on this with other types of streams
using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
  string line;
  while ((line = sr.ReadLine()) != null)
  {
    sb.AppendLine(line);
  }
}

// This throws an exception
sb.ToString();

方法三

using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (StreamReader sr = new StreamReader(fs, Encoding.Unicode))
{
  int initialSize = (int)fs.Length / 2;  // Comes to a value of 73285158 with my test file
  var sb = new StringBuilder(initialSize); // This throws an exception

  string line;
  while ((line = sr.ReadLine()) != null)
  {
    sb.AppendLine(line);
  }

  sb.ToString();
}

那么,我该怎么做才能将这个文件加载到一个字符串变量中呢?

编辑:根据评论添加了解决问题的额外尝试。

到目前为止,您的两次尝试都将文件视为 UTF-8 文件。在最好的情况下,这将占用两倍的内存——而且它很可能基本上是无效数据(如 UTF-8)。您应该尝试指定编码:

var text = File.ReadAllText(path, Encoding.Unicode);

如果这不起作用,您可以在第二个代码上尝试变体,但将编码指定为 StreamReader(并且可能忽略 BufferedStream - 我不认为它'我会在这里帮助你),并指定 StringBuilder 的初始容量,等于文件大小的一半。

编辑:如果此行抛出异常:

var sb = new StringBuilder(initialSize);

...那你就没有机会了。您无法分配足够的连续内存。

可能发现您可以使用List<string>代替:

var lines = File.ReadLines(path).ToList();

...因为至少你有很多 little 对象。它将占用更多内存,但不需要那么多的连续内存。这是假设您确实需要 内存中的整个文件一次。如果您可以改为流式传输数据,那将是一个更好的选择。

在小型控制台应用程序中,我可以使用 File.ReadAllText 读取相同大小的文件,同时使用 32 位和 64 位 CLR...所以它可能是你的物理内存和你在程序中做的其他事情的问题。

我也在尝试找到一种方法,在加载时以最少的内存使用量将文件加载到字符串中。 但是我看到的所有方法都有 StringBuilder:首先它收集 StringBuilder 中的所有内容,然后调用 StringBuilder.ToString(),因此在这个过程中我们将所有相同的字符重复两次。

这意味着,对于 UTF16 文件 - 峰值内存使用量 =(文件大小)* 2 字节,对于 UTF8 文件(假设所有文本都是 ASCII)峰值内存使用量 =(文件大小)* 4 字节。最后,我们当然会使用(字符串长度)* 2 个字节的内存。

  • StreamReader.ReadToEnd() - 使用 StringBuilder
  • File.ReadAllText() - 使用 StreamReader.ReadToEnd() - 使用 StringBuilder