【问题标题】:Load a large text file into a string将大文本文件加载到字符串中
【发布时间】:2015-04-05 16:42:11
【问题描述】:

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

我知道这是一个巨大的字符串,当然不是我想要的做事方式。但是,如果不对即将推出的应用程序进行大量真正深刻的更改,目前我真的无能为力。该文件中没有一组均匀分布的行。一行可以包含整个文件大小的 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();

方法3

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();
}

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

编辑:添加了基于 cmets 解决问题的额外尝试。

【问题讨论】:

  • 作为附加措施,您可能希望强制使用 x64 而不是 AnyCPU/首选 x86... 32 位进程中的内存碎片是大型分配失败的常见原因。
  • 不幸的是,这必须是一个 32 位应用程序。
  • 便宜的方法来帮助stackoverflow.com/questions/14186256/…我已经将它用于图像,但更好地解决核心问题
  • 如果您正在处理有限的资源,最好的方法可能是打开一个 FileStream 并使用流阅读器对其进行迭代。有点符合“你怎么吃大象?一次一口”的原则。 (双关语)

标签: c# string file-handling


【解决方案1】:

到目前为止,您的两次尝试都将文件视为 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...所以这可能是您的问题物理内存以及您在程序中所做的其他事情。

【讨论】:

  • 有趣 - 所以你是说在内存中加载一个 150 mb 的字符串是可以的,只要 RAM 中有 150 兆可用?这有上限吗?只要有内存,.NET 是否允许您在字符串中存储任何大小?
  • @spike:嗯,有一些事情需要考虑。它可能暂时占用比这更多的空间 - 如果你不走运,你可能需要近 3 倍的内存,就好像你在完成读取数据之前不久就用完了缓冲区中的空间,它'将分配一个两倍大的缓冲区 - 然后当您调用 ToString 时,它将将该缓冲区的相关部分复制到一个新字符串中。如果我没记错的话,64 位 CLR 的每个对象限制为 2GB,或者 32 位 CLR 中的每个对象限制为 1GB(但细节可能略有偏差)。
  • @spike:您应该能够将 150MB 文件作为字符串加载,但这不一定是一个好主意……(根据 Alexei 的评论,如果可以的话,请使用 64 位 CLR。 )
  • 谢谢!当然,我会尽量避免它;)我只是想知道限制在哪里以及为什么。
  • @Jon Skeet:我尝试了你的两个建议。 ReadAllText 版本给了我内存不足错误。用文件大小的 1/2 实例化 StringBuilder 也会抛出一个。如果我不设置初始容量,那么我会在帖子中的第二种方法中遇到错误。
【解决方案2】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-16
    • 2021-04-30
    • 2018-10-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多