Nic*_*Lee 89 .net c# stream streamreader large-files
我有一个很好的任务,就是如何处理大型文件被加载到我们应用程序的脚本编辑器中(就像我们用于快速宏的内部产品的VBA一样).大多数文件大约300-400 KB,这是很好的加载.但是当它们超过100 MB时,这个过程很难(正如你所期望的那样).
发生的事情是将文件读取并推入RichTextBox然后导航 - 不要过于担心这部分.
编写初始代码的开发人员只是使用StreamReader并且正在执行
[Reader].ReadToEnd()
Run Code Online (Sandbox Code Playgroud)
这可能需要很长时间才能完成.
我的任务是打破这段代码,将其以块的形式读入缓冲区并显示一个带有取消选项的进度条.
一些假设:
现在提问:
这些(在您的专业意见中)是好主意吗?我过去曾经有一些问题从Streams读取内容,因为它总会错过最后几个字节或者其他东西,但如果是这样的话,我会问另一个问题.
Eri*_* J. 168
您可以使用BufferedStream提高读取速度,如下所示:
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)
{
}
}
Run Code Online (Sandbox Code Playgroud)
2013年3月更新
我最近编写了用于读取和处理(搜索文本)1 GB-ish文本文件的代码(比这里涉及的文件大得多),并通过使用生产者/消费者模式实现了显着的性能提升.生产者任务使用文本行读取BufferedStream并将它们交给执行搜索的单独的消费者任务.
我用它来学习TPL数据流,这非常适合快速编码这种模式.
为什么BufferedStream更快
缓冲区是内存中用于缓存数据的字节块,从而减少了对操作系统的调用次数.缓冲区可提高读写性能.缓冲区可用于读取或写入,但不能同时使用.BufferedStream的Read和Write方法自动维护缓冲区.
2014年12月更新:您的里程可能会有所不同
根据注释,FileStream应该在内部使用BufferedStream.在首次提供此答案时,我通过添加BufferedStream测量了显着的性能提升.当时我在32位平台上瞄准.NET 3.x. 今天,在64位平台上面向.NET 4.5,我没有看到任何改进.
有关
我遇到了一个案例,从ASP.Net MVC操作将大量生成的CSV文件传输到Response流非常慢.在这种情况下,添加BufferedStream可将性能提高100倍.有关更多信息,请参阅非缓冲输出非常慢
小智 17
如果您阅读本网站上的性能和基准测试统计数据,您将看到最快的阅读方式(因为阅读,编写和处理都不同),文本文件是以下代码片段:
using (StreamReader sr = File.OpenText(fileName))
{
string s = String.Empty;
while ((s = sr.ReadLine()) != null)
{
//do your stuff here
}
}
Run Code Online (Sandbox Code Playgroud)
所有大约9种不同的方法都是基准标记,但是大多数时候这个方法似乎都提前出来,甚至像其他读者所提到的那样执行缓冲读取器.
Chr*_*ter 15
您说在加载大文件时,系统会要求您显示进度条.这是因为用户真的想要查看文件加载的确切百分比,还是因为他们想要视觉反馈才能发生某些事情?
如果后者是真的,则解决方案变得更加简单.只需reader.ReadToEnd()在后台线程上执行,并显示一个选取框类型的进度条而不是正确的进度条.
我提出这一点,因为根据我的经验,这种情况经常发生.当您编写数据处理程序时,用户肯定会对%完整数字感兴趣,但对于简单但缓慢的UI更新,他们更可能只想知道计算机没有崩溃.:-)
小智 8
对于二进制文件,我发现最快的阅读方式就是这个.
MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(file);
MemoryMappedViewStream mms = mmf.CreateViewStream();
using (BinaryReader b = new BinaryReader(mms))
{
}
Run Code Online (Sandbox Code Playgroud)
在我的测试中它快了几百倍.
使用后台工作程序并只读取有限数量的行.仅在用户滚动时阅读更多内容.
并尝试永远不要使用ReadToEnd().这是你认为"他们为什么要成功?"的功能之一; 这是一个脚本小子的助手,可以很好地处理小事情,但正如你所看到的那样,对于大文件来说它很糟糕......
那些告诉你使用StringBuilder的人需要更频繁地阅读MSDN:
性能注意事项
Concat和AppendFormat方法都将新数据连接到现有的String或StringBuilder对象.String对象并置操作始终从现有字符串和新数据创建新对象.StringBuilder对象维护一个缓冲区以容纳新数据的串联.如果房间可用,新数据将附加到缓冲区的末尾; 否则,分配一个新的较大缓冲区,将原始缓冲区中的数据复制到新缓冲区,然后将新数据附加到新缓冲区.String或StringBuilder对象的串联操作的性能取决于内存分配发生的频率.
字符串连接操作始终分配内存,而StringBuilder连接操作仅在StringBuilder对象缓冲区太小而无法容纳新数据时分配内存.因此,如果连接固定数量的String对象,则String类更适合并置操作.在这种情况下,编译器甚至可以将单个连接操作组合成单个操作.如果连接任意数量的字符串,则StringBuilder对象最好用于连接操作; 例如,如果循环连接随机数量的用户输入字符串.
这意味着巨大的内存分配,大量使用交换文件系统,模拟硬盘驱动器的部分就像RAM内存,但硬盘驱动器非常慢.
StringBuilder选项看起来很适合将系统用作单用户的用户,但是如果有两个或更多用户同时读取大文件,则会出现问题.
看看下面的代码片段。你提到过Most files will be 30-40 MB。这声称在英特尔四核上在 1.4 秒内读取了 180 MB:
private int _bufferSize = 16384;
private void ReadFile(string filename)
{
StringBuilder stringBuilder = new StringBuilder();
FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
using (StreamReader streamReader = new StreamReader(fileStream))
{
char[] fileContents = new char[_bufferSize];
int charsRead = streamReader.Read(fileContents, 0, _bufferSize);
// Can't do much with 0 bytes
if (charsRead == 0)
throw new Exception("File is 0 bytes");
while (charsRead > 0)
{
stringBuilder.Append(fileContents);
charsRead = streamReader.Read(fileContents, 0, _bufferSize);
}
}
}
Run Code Online (Sandbox Code Playgroud)
这应该足以让你入门.
class Program
{
static void Main(String[] args)
{
const int bufferSize = 1024;
var sb = new StringBuilder();
var buffer = new Char[bufferSize];
var length = 0L;
var totalRead = 0L;
var count = bufferSize;
using (var sr = new StreamReader(@"C:\Temp\file.txt"))
{
length = sr.BaseStream.Length;
while (count > 0)
{
count = sr.Read(buffer, 0, bufferSize);
sb.Append(buffer, 0, count);
totalRead += count;
}
}
Console.ReadKey();
}
}
Run Code Online (Sandbox Code Playgroud)
所有优秀的答案!然而,对于寻找答案的人来说,这些似乎有些不完整。
由于标准字符串只能为 X 大小、2Gb 到 4Gb,具体取决于您的配置,这些答案并不能真正满足 OP 的问题。一种方法是使用字符串列表:
List<string> Words = new List<string>();
using (StreamReader sr = new StreamReader(@"C:\Temp\file.txt"))
{
string line = string.Empty;
while ((line = sr.ReadLine()) != null)
{
Words.Add(line);
}
}
Run Code Online (Sandbox Code Playgroud)
有些人可能希望在处理时对行进行标记和分割。字符串列表现在可以包含大量文本。
虽然获得最多支持的答案是正确的,但它缺乏多核处理的使用。就我而言,有 12 个核心,我使用 PLink:
Parallel.ForEach(
File.ReadLines(filename), //returns IEumberable<string>: lazy-loading
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
(line, state, index) =>
{
//process line value
}
);
Run Code Online (Sandbox Code Playgroud)
值得一提的是,我在面试时遇到了一个问题,询问出现次数最多的 10 种情况:
var result = new ConcurrentDictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
Parallel.ForEach(
File.ReadLines(filename),
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
(line, state, index) =>
{
result.AddOrUpdate(line, 1, (key, val) => val + 1);
}
);
return result
.OrderByDescending(x => x.Value)
.Take(10)
.Select(x => x.Value);
Run Code Online (Sandbox Code Playgroud)
Benchmarking:
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-8700K CPU 3.70GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
[Host] : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT
DefaultJob : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT
| 方法 | 意思是 | 错误 | 标准差 | 第0代 | 第一代 | 第2代 | 已分配 |
|---|---|---|---|---|---|---|---|
| 获取热门词同步 | 33.03秒 | 0.175秒 | 0.155秒 | 1194000 | 314000 | 7000 | 7.06GB |
| 获取热门单词并行 | 10.89秒 | 0.121秒 | 0.113秒 | 1225000 | 354000 | 8000 | 7.18GB |
正如您所看到的,性能提高了 75%。
但请注意,7Gb 会立即加载到内存中,并且由于它是一个 blob,因此会给 GC 带来太大的压力。
| 归档时间: |
|
| 查看次数: |
176590 次 |
| 最近记录: |