C# 在C中读取带有流的大型文本文件#
我有一个很好的任务,就是研究如何处理加载到应用程序脚本编辑器中的大文件(就像我们的内部产品快速宏一样)。大多数文件的大小约为300-400KB,可以很好地加载。但是当它们超过100MB时,这个过程会很困难(正如您所预料的) 所发生的事情是,文件被读取并放入一个RichTextBox中,然后进行导航-不要太担心这一部分 编写初始代码的开发人员只需使用StreamReader并执行以下操作C# 在C中读取带有流的大型文本文件#,c#,.net,stream,streamreader,large-files,C#,.net,Stream,Streamreader,Large Files,我有一个很好的任务,就是研究如何处理加载到应用程序脚本编辑器中的大文件(就像我们的内部产品快速宏一样)。大多数文件的大小约为300-400KB,可以很好地加载。但是当它们超过100MB时,这个过程会很困难(正如您所预料的) 所发生的事情是,文件被读取并放入一个RichTextBox中,然后进行导航-不要太担心这一部分 编写初始代码的开发人员只需使用StreamReader并执行以下操作 [Reader].ReadToEnd() 这可能需要很长时间才能完成 我的任务是分解这段代码,将其分块读入缓
[Reader].ReadToEnd()
这可能需要很长时间才能完成
我的任务是分解这段代码,将其分块读入缓冲区,并显示一个带有取消选项的progressbar
一些假设:
- 大多数文件将为30-40MB
- 文件的内容是文本(不是二进制),有些是Unix格式,有些是DOS格式
- 一旦检索到内容,我们就知道使用了什么终止符
- 一旦加载了richtextbox中渲染所需的时间,就没有人关心了。这只是文本的初始加载
- 我可以简单地使用StreamReader,然后检查Length属性(so ProgressMax)并发出一个设置缓冲区大小的Read,并在后台工作程序中以while循环进行迭代,这样它就不会阻塞主UI线程了吗?完成后,将stringbuilder返回到主线程
- 内容将发送给StringBuilder。如果长度可用,我可以用流的大小初始化StringBuilder吗
这些(在你的专业意见中)是好主意吗?过去我在从流中读取内容时遇到过一些问题,因为它总是会丢失最后几个字节或其他内容,但如果是这种情况,我会问另一个问题。使用后台工作程序,只读取有限的行数。仅当用户滚动时阅读更多内容 并且尽量不要使用ReadToEnd()。这是你认为“他们为什么要这么做”的功能之一;它是一个可以处理小事情的助手,但正如您所看到的,它对于大文件来说很糟糕 那些告诉您使用StringBuilder的人需要更频繁地阅读MSDN: 性能注意事项
Concat和AppendFormat方法都将新数据连接到现有字符串或StringBuilder对象。字符串对象连接操作总是从现有字符串和新数据创建新对象。StringBuilder对象维护一个缓冲区以容纳新数据的串联。如果有可用空间,新数据将附加到缓冲区的末尾;否则,将分配一个新的、更大的缓冲区,将原始缓冲区中的数据复制到新缓冲区,然后将新数据追加到新缓冲区。 字符串或StringBuilder对象的连接操作的性能取决于内存分配的频率
字符串连接操作始终分配内存,而StringBuilder连接操作仅在StringBuilder对象缓冲区太小而无法容纳新数据时分配内存。因此,如果连接了固定数量的字符串对象,则字符串类更适合用于连接操作。在这种情况下,编译器甚至可以将单个串联操作组合成单个操作。如果串接任意数量的字符串,则串接操作最好使用StringBuilder对象;例如,如果一个循环连接了随机数目的用户输入字符串 这意味着巨大的内存分配,即交换文件系统的大量使用,它模拟硬盘的各个部分,使其与RAM内存类似,但硬盘速度非常慢
StringBuilder选项对于以单用户身份使用系统的用户来说很合适,但是当两个或多个用户同时读取大文件时,就会出现问题。您最好使用内存映射文件处理。。内存映射文件支持将出现在.NET4中(我想…我是通过其他人的讨论听到的),因此这个包装器使用p/invokes来完成同样的工作
编辑:请参见上的此处了解它的工作原理,这里的条目说明了它在即将发布的.NET 4中是如何完成的。我前面给出的链接是一个围绕pinvoke的包装器来实现这一点。您可以将整个文件映射到内存中,并在滚动文件时像滑动窗口一样查看它。查看以下代码片段。您已经提到,
大多数文件的大小将为30-40MB
。这声称在英特尔四核上可在1.4秒内读取180MB的数据:
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);
}
}
}
这应该足以让您开始
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();
}
}
您说您被要求在加载大文件时显示进度条。这是因为用户真的希望看到文件加载的确切百分比,还是仅仅因为他们希望看到某些事情发生的视觉反馈 如果后者是真的,那么解决方案就会简单得多。只需在后台线程上执行
reader.ReadToEnd()
,并显示一个字幕类型的进度条,而不是正确的进度条
我提出这一点是因为根据我的经验,情况往往如此。当您编写数据处理程序时,用户肯定会对完成百分比感兴趣,但对于简单但缓慢的UI更新,他们更可能只想知道计算机没有崩溃。:-) 迭代器可能非常适合这类工作:
public static IEnumerable<int> LoadFileWithProgress(string filename, StringBuilder stringData)
{
const int charBufferSize = 4096;
using (FileStream fs = File.OpenRead(filename))
{
using (BinaryReader br = new BinaryReader(fs))
{
long length = fs.Length;
int numberOfChunks = Convert.ToInt32((length / charBufferSize)) + 1;
double iter = 100 / Convert.ToDouble(numberOfChunks);
double currentIter = 0;
yield return Convert.ToInt32(currentIter);
while (true)
{
char[] buffer = br.ReadChars(charBufferSize);
if (buffer.Length == 0) break;
stringData.Append(buffer);
currentIter += iter;
yield return Convert.ToInt32(currentIter);
}
}
}
}
加载文件时,迭代器将返回从0到100的进度编号,您可以使用该编号更新进度条。循环完成后,StringBuilder将包含文本文件的内容
此外,由于您需要文本,我们可以使用BinaryReader读入字符,这将确保您的缓冲区
string filename = "C:\\myfile.txt";
StringBuilder sb = new StringBuilder();
foreach (int progress in LoadFileWithProgress(filename, sb))
{
// Update your progress counter here!
}
string fileData = sb.ToString();
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)
{
}
}
using (StreamReader sr = File.OpenText(fileName))
{
string s = String.Empty;
while ((s = sr.ReadLine()) != null)
{
//do your stuff here
}
}
MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(file);
MemoryMappedViewStream mms = mmf.CreateViewStream();
using (BinaryReader b = new BinaryReader(mms))
{
}
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);
}
}
Parallel.ForEach(
File.ReadLines(filename), //returns IEumberable<string>: lazy-loading
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
(line, state, index) =>
{
//process line value
}
);
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);