高级文件阅读

6

我相信我们都很熟悉并且可能会使用书籍、在线等提供的大量代码来使用C#读取文件。像这样简单的操作...

StringBuilder fiContents = new StringBuilder();
using (StreamReader fi = new StreamReader(@"C:\a_file.txt"))
{
    while (!fi.EndOfStream)
    {
        fiContents.Append(fi.ReadLine); 
    }
}

或者只是像这样简短的内容...
using (StreamReader fi = new StreamReader(@"C:\a_file.txt"))
    fiContents.Append(fi.ReadToEnd());

现在让我们暂时变成超级赛亚人,做一些非常高级的东西,比如拥有一个BackgroundWorker,它将允许我们显示一个加载图像(这是我将使用的),提供一个进程倒计时计时器或ProgressBar
public void ReadFile(string filename)
{
    BackgroundWorker procFile = new BackgroundWorker();
    // Progress 1: If we want to show the progress we need to enable the following property
    // procFile.WorkerReportsProgress = true;

    profile.DoWork += new DoWorkEventHandler((object obj, DoWorkEventArgs ev) =>
    {
        StringBuilder fiContents = new StringBuilder();

        using (StreamReader fi = new StreamReader(filename))
        {
            while (!fi.EndOfStream)
            {
                // Progress 2: Report the progress, this will be dealt with by the respective handler (below).
                // procFile.ReportProgress((int)(fi.BaseStream.Length / fi.BaseStream.Position) / 100);

                fiContents.Append(fi.ReadLine);
            }
        }

        ev.Result = fiContents;
    }

    /* Progress 3: The handler below will take care of updating the progress of the file as it's processed. 
    procFile.ProgressChanged += new ProgressChangedEventHandler((object obj, ProgressChangedEventArgs ev) =>
    {
        // Progress 4: Do something with the value, such as update a ProgressBar. 
        // ....
    }
    */

    procFile.RunWorkerCompleted += new RunWorkerCompletedEventHandler((object obj, RunWorkerCompletedEventArgs ev) =>
    {
         // Do something with the result (ev.Result), bearing in mind, it is a StringBuilder and the ev.Result is an object. 
         StringBuilder result = ev.Result as StringBuilder; 

         // ....
    }
}

时间到了实际问题...上面是热身之作,用来展示对问题的理解程度,以免遇到这些答案。

我基本上在执行上述最后一个代码示例(即使用BackgroundWorker)并将读取的内容转储到RichTextBox中。真的很简单。

然而,我面临的问题是处理大文件(例如 ~222MB)。情况是只需取 .txt 文件,读取它,将通过StringBuilder构建的结果推送到RichTextBox中。它无法加载该文件,我得到一个OutOfMemoryException。一种解决方法是迭代字符串,并从StringBuilder中添加每个字符(作为char)。

我始终使用最基本和直接的方法读取文件(如上述示例),但是否有人有任何指导如何改进这一点?如何处理极大的文件等等。

即使是作为讨论的话题,我也欢迎您的想法。

+++++ +++++ +++++ +++++

编辑1(@TaW):当尝试将string放入RichTextBox时抛出异常...

FileProcessing.RunWorkerCompleted += new RunWorkerCompletedEventArgs((object obj, RunWorkerCompletedEventArgs e) =>
{
    // 'Code' is the RichTextBox in question...

    Code.Text = "";

    if (e.Result is StringBuilder)
    {
        Code.Text = (e.Result as StringBuilder).ToString();
    }
}

个人而言,我只是使用 File.ReadAllText(filename) ,但是我很懒。 - user1228
简短的回答是,要加载一个太大无法放入内存的文件,你不能一次性全部加载。只加载当前在滚动控件中可见的部分文件是一个常见的解决方案。 - Kendall Frey
1
读取一个200MB的文件应该只需要很短的时间,进度条和后台工作线程是不必要的。 - Lasse V. Karlsen
@KendallFrey:我很感激您提供的文件大小示例非常大,因此我不能简单地将其全部加载到内存中(或者也许不应该)。您能否给出一个带有滚动解决方案的代码示例? - user1092809
1
我认为富文本框没有虚拟化,因为虚拟化最适用于IList,而不是IEnumerable(字符串可以被视为IEnumerable<char>)。在幕后,虚拟化利用索引器,因此不需要迭代整个集合即可到达给定位置(我们要显示的部分)。想象一下尝试通过IEnumerable向后滚动...这是不可能的,除非从头开始。 - Jason Down
显示剩余9条评论
3个回答

2
你是否有使用RichTextBox控件显示内容的限制?该控件不是虚拟化的,会导致性能问题(并且根据外观来看会出现内存错误问题)。
有一系列更适合显示大型文档的文档查看控件可供选择。根据你的需求,有不同类型的控件(固定、通过页面或滚动流动)。此外,你还可以获得搜索、打印、缩放和其他一些常用于查看大型文档的功能。

我正在开发一个脚本编辑器(带有高亮和智能感知功能),现在已经完成,需要一个RichTextBox来实现高亮和其他功能。因此,它们将是纯文本文件。 - user1092809

0

你尝试过 MemoryMapped 吗?
它是一个非常有用的库,可以处理大文件。


0

这不是关于高级阅读,而是关于达到(Winforms)控件的容量限制。也许你可以在WPF中使其工作,但在Winforms中,无论是RichTextBox还是TextBox都无法容纳如此多的行/文本。

我建议您重新设计以将数据以较小的块呈现给用户。并不是他们想要滚动浏览100,000+行。在内存中处理它们不是问题;在这里,200MB根本不算大;例如,您可以轻松地在内存中搜索等。


我提出的问题,虽然是通过一个问题(即WinForm控件的限制)来提出的,但是它是关于C#文件I/O的更进一步的技术和讨论。因此我提供了很多代码示例,以演示我熟悉的一些技巧,也是大多数人使用的方法。我完全同意你的第二点。 - user1092809

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接