C# 在C中读取带有流的大型文本文件#

C# 在C中读取带有流的大型文本文件#,c#,.net,stream,streamreader,large-files,C#,.net,Stream,Streamreader,Large Files,我有一个很好的任务,就是研究如何处理加载到应用程序脚本编辑器中的大文件(就像我们的内部产品快速宏一样)。大多数文件的大小约为300-400KB,可以很好地加载。但是当它们超过100MB时,这个过程会很困难(正如您所预料的) 所发生的事情是,文件被读取并放入一个RichTextBox中,然后进行导航-不要太担心这一部分 编写初始代码的开发人员只需使用StreamReader并执行以下操作 [Reader].ReadToEnd() 这可能需要很长时间才能完成 我的任务是分解这段代码,将其分块读入缓

我有一个很好的任务,就是研究如何处理加载到应用程序脚本编辑器中的大文件(就像我们的内部产品快速宏一样)。大多数文件的大小约为300-400KB,可以很好地加载。但是当它们超过100MB时,这个过程会很困难(正如您所预料的)

所发生的事情是,文件被读取并放入一个RichTextBox中,然后进行导航-不要太担心这一部分

编写初始代码的开发人员只需使用StreamReader并执行以下操作

[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);