实现大型文本文件阅读器的最佳策略

本文关键字:最佳 策略 大型 文本 文件 实现 | 更新日期: 2023-09-27 18:26:11

我们有一个应用程序,它将处理步骤记录到文本文件中。这些文件在实施和测试过程中用于分析问题。每个文件的大小最高可达10MB,最多包含100000行文本。

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

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

文件将不会被编辑!

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

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

这种方法的一个问题是如何从存储的字符串中获取特定的行。

如果有人有任何想法,可以用我的方法突出问题,那么谢谢。

实现大型文本文件阅读器的最佳策略

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

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

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

这里有一种方法,可以在具有多核的现代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框架,有不同的方法可以做到这一点。

该解决方案假设您可以通过对文件进行一次正向传递来提取所需的行。如果您需要执行多次传递(可能基于用户输入),则可能需要将文件中的所有行缓存到内存中。缓存10MB并不多,但假设您决定搜索多个文件。缓存1GB可能会使功能强大的计算机不堪重负,但正如我所建议的那样,使用更少的内存和更多的CPU将使您能够在现代台式电脑上在合理的时间内搜索非常大的文件。

我想,当一个人有多GB的RAM可用时,人们自然会倾向于"将整个文件加载到内存中"的路径,但这里有人真的对这个问题的理解如此肤浅感到满意吗?当这个家伙想要加载一个4GB的文件时会发生什么?(是的,可能不太可能,但编程通常是关于可扩展的抽象,而将整个东西加载到内存中的快速修复方法是不可扩展的。)

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

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

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

[/rant]

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