将大文本文件加载到字符串中
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
我想将一个 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