【问题标题】:Best strategy to implement reader for large text files为大型文本文件实现阅读器的最佳策略
【发布时间】:2012-03-01 14:11:00
【问题描述】:

我们有一个将其处理步骤记录到文本文件中的应用程序。这些文件在实施和测试期间用于分析问题。每个文件最大为 10MB,最多包含 100,000 行文本。

目前对这些日志的分析是通过打开文本查看器(Notepad++ 等)并根据问题查找特定字符串和数据来完成的。

我正在构建一个有助于分析的应用程序。它将使用户能够阅读文件、搜索、突出显示特定字符​​串以及与隔离相关文本相关的其他特定操作。

文件不会被编辑!

在尝试一些概念时,我立即发现 TextBox(或 RichTextBox)不能很好地处理大文本的显示。我设法使用 DataGridView 实现了一个性能可接受的查看器,但该控件不支持特定字符串的颜色突出显示。

我现在正在考虑将整个文本文件作为字符串保存在内存中,并且只在 RichTextBox 中显示数量非常有限的记录。对于滚动和导航,我想添加一个独立的滚动条。

我在使用这种方法时遇到的一个问题是如何从存储的字符串中获取特定的行。

如果有人有任何想法,可以指出我的方法存在的问题,然后谢谢。

【问题讨论】:

  • 描述特定行。除此之外,LINQ 易于维护和阅读,而且速度可能足够快。 (a similar question yesterday)
  • 特定字符串描述系统执行的操作。分析器将搜索它们以通过日志文件了解系统的处理。我的问题不在于搜索大文件,而在于向用户显示日志文件(或其中的一部分)。

标签: c# winforms


【解决方案1】:

我建议将整个内容加载到内存中,但作为字符串集合而不是单个字符串。这很容易做到:

string[] lines = File.ReadAllLines("file.txt");

然后您可以使用 LINQ 搜索匹配的行,轻松显示它们等。

【讨论】:

    【解决方案2】:

    这是一种可以很好地在具有多核的现代 CPU 上扩展的方法。

    您创建一个迭代器块,从文本文件(或多个文本文件,如果需要)产生行:

    IEnumerable<String> GetLines(String fileName) {
      using (var streamReader = File.OpenText(fileName))
        while (!streamReader.EndOfStream)
          yield return streamReader.ReadLine();
    }
    

    然后您可以使用 PLINQ 并行搜索行。如果您拥有现代 CPU,这样做可以大大加快搜索速度。

    GetLines(fileName)
      .AsParallel()
      .AsOrdered()
      .Where(line => ...)
      .ForAll(line => ...);
    

    您在Where 中提供了一个与您需要提取的行匹配的谓词。然后,您向ForAll 提供一个操作,将这些行发送到它们的最终目的地。

    这是您需要做的简化版本。您的应用程序是 GUI 应用程序,您无法在主线程上执行搜索。您必须为此启动后台任务。如果您希望此任务可取消,则需要在 GetLines 方法中的 while 循环中检查取消令牌。

    ForAll 将调用线程池中线程的操作。如果要将匹配行添加到用户界面控件,则需要确保在用户界面线程上更新此控件。根据您使用的 UI 框架,有不同的方法可以做到这一点。

    此解决方案假定您可以通过对文件进行一次前向传递来提取所需的行。如果您需要根据用户输入进行多次传递,则可能需要将文件中的所有行缓存在内存中。缓存 10 MB 并不多,但假设您决定搜索多个文件。缓存 1 GB 甚至可以使功能强大的计算机承受压力,但我建议使用更少的内存和更多的 CPU,这将使您能够在合理的时间内在现代台式 PC 上搜索非常大的文件。

    【讨论】:

    • 澄清一下,您的意思是我不将文件加载到内存中,而是根据需要直接从驱动器中搜索?我看到的一个问题是如何加载一系列行(显示第 10 - 30 行)。
    • @Yoav:如果需要,您可以让GetLines 返回一个包含行号和行号的类。然后,您可以在谓词中的行号上包含一个条件。无论如何,为什么不尝试使用您认为最方便实施的任何解决方案的简单原型呢?而且,如果这太慢或使用太多内存,请尝试对其进行优化。当我不得不搜索数 GB 的日志数据时,我就是这样做的。我希望应用程序受 I/O 限制,而实际上它受 CPU 限制,允许我通过在我的 8 路机器上使用 PLINQ 将速度提高近 8 倍。
    • 请注意,.Net Framework 4.0 引入了File.ReadLines。这相当于功能上的GetLines
    【解决方案3】:

    我想当一个人有数 GB 的可用 RAM 时,自然会倾向于“将整个文件加载到内存中”路径,但是这里有人真的对这个问题的这种肤浅理解感到满意吗?当这个人想要加载一个 4 GB 的文件时会发生什么? (是的,可能不太可能,但编程通常是关于可扩展的抽象,并且将整个事物加载到内存中的快速修复是不可扩展的。)

    当然,存在相互竞争的压力:您是否需要昨天的解决方案,或者您是否有足够的时间深入研究问题并学习新知识?该框架还通过将块模式文件呈现为流来影响您的思维......您必须检查流的 BaseStream.CanSeek 值,如果这是真的,请访问 BaseStream.Seek() 方法以获得随机访问。不要误会,我绝对喜欢 .NET 框架,但我看到一个建筑工地,一群“木匠”无法为房屋搭建框架,因为空气压缩机坏了,他们没有知道如何使用锤子。打蜡、脱蜡、教人钓鱼等。

    因此,如果您有时间,请查看滑动窗口。您可能可以通过使用内存映射文件(让框架/操作系统管理滑动窗口)以简单的方式做到这一点,但有趣的解决方案是自己编写。基本思想是,您在任何时候都只有一小部分文件加载到内存中(在您的界面中可见的文件部分,两侧可能有一个小缓冲区)。在文件中向前移动时,您可以保存每行开头的偏移量,以便您可以轻松地查找文件的任何较早部分。

    是的,这会影响性能...欢迎来到现实世界,在这个世界中,人们面临着各种要求和限制,并且必须在时间和内存利用率之间找到可接受的平衡。这就是编程的乐趣……找出可以达到目标的各种方式,并了解各种路径之间的权衡。这就是你如何超越办公室里那个将每个问题都视为钉子的人的技能水平,因为他只知道如何使用锤子。

    [/咆哮]

    【讨论】:

      【解决方案4】:

      我建议在 .NET 4 中使用 MemoryMappedFile(或通过以前版本中的 DllImport)来处理屏幕上可见的文件的一小部分,而不是在加载整个文件时浪费内存和时间。

      【讨论】:

        猜你喜欢
        • 2011-02-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多