从一个行一次读取多个文件的特定行的最快方法

时间:2013-10-16 17:09:41

标签: c# file-io .net-4.5 streamreader

我有一个基于文本的数据库,表示按时间戳排序的日志。出于测试目的,我的数据库大约有10,000行,但这个数字可能更大。它的格式为:

  

primary_key,source_file,line_num
  1, cpu.txt, 2
  2, ram.txt, 3
  3, cpu.txt, 3

我查询数据库,当我读取结果时,我想将实际数据添加到我可以显示的字符串中。上例中的实际数据将是来自cpu.txt的第2行的内容,后面是来自ram.txt的第3行的内容,等等。行内容可能很长。

一个重要的注意事项是每个文件的行号都是有序的。也就是说,下次我在数据库中遇到cpu.txt条目时,它将以行4作为行号。但是,我可能会在ram.txt,harddrive.txt,graphics.txt等数千个其他条目之后看到cpu.txt条目。

我考虑过使用以下代码中的内容:

StringBuilder odbcResults = new StringBuilder();
OdbcDataReader dbReader = com.ExecuteReader();  // query database
while (dbReader.Read())
{
   string fileName = dbReader[1].ToString(); // source file
   int fileLineNum = int.Parse(dbReader[2].ToString());  // line number in source file

   odbcResults.Append(File.ReadLines(fileName).Skip(fileLineNum).First());
}

但是,每次迭代后,File.ReadLines()都不会想要处理它的TextReader吗?不是很有效率?

我也有这个想法,为Dict中需要读取的每个文件保留一个StreamReader:

Dictionary<string, StreamReader> fileReaders = new Dictionary<string, StreamReader>();
StringBuilder odbcResults = new StringBuilder();
OdbcDataReader dbReader = com.ExecuteReader();
while (dbReader.Read())
{
   string fileName = dbReader[1].ToString(); // source file
   int fileLineNum = int.Parse(dbReader[2].ToString());  // line number in source file

   if (!fileReaders.ContainsKey(fileName))
   {
      fileReaders.Add(fileName, new StreamReader(fileName));
   }

   StreamReader fileReader = fileReaders[fileName];
   // don't have to worry about positioning? Lines consumed consecutively
   odbcResults.Append(fileReader.ReadLine());
}
// can't forget to properly Close() and Dispose() of all fileReaders

您是否同意以上任何一个例子,或者是否有更好的方法?
对于第二个例子,我假设StreamReader将记住它的最后位置 - 我相信它会保存在BaseStream中。

我已阅读How do I read a specified line in a text file?Read text file at specific lineStreamReader and seeking(第一个答案提供了指向具有定位功能的自定义StreamReader类的链接,但我只知道我需要的行号是,而不是抵消)但不要认为他们具体回答我的问题。

2 个答案:

答案 0 :(得分:2)

如果您可以保证您的行引用在文件中是严格顺序的(即,在您要求第n行后总是要求第n + 1行),那么您可以选择保留StreamReader字典实例看起来是个好主意。

如果您可能要求行n,那么行n + x(其中x是某个正数&gt; = 1),然后我将StreamReader包裹在跟踪当前行的对象中number并有一个方法GetLine(int lineNo),它将返回请求的行号。假设请求的行号大于当前行号(不允许向后读取)。

你不必担心定位。这是为你处理的,因为你正在顺序阅读。

答案 1 :(得分:1)

听起来你似乎想要在内存中(用于在文本框中显示)用户选择的所有内容 - 因此无论如何这是可行的自然边界。我建议采用以下方法:

  • 从数据库中读取所有匹配的元数据(即在用户指定的时间范围内)到列表中。保留一组我们需要阅读的文件。
  • 创建一个与列表大小相同的新数组 - 这将保存最终数据
  • 一次浏览一个所需的文件:
    • 打开文件,记住我们在第0行
    • 迭代元数据列表。对于与我们当前打开的文件匹配的每个条目,请向前读到右边的行,并填充与我们正在查看的列表条目对应的最终数据数组元素。我们只需要向前阅读,因为我们仍然按时间戳顺序。
    • 关闭文件

此时,应该完全填充“最终数据阵列”。您只需要一次打开一个文件,而您永远不需要读取整个文件。我认为这比打开文件字典更简单 - 除了其他任何东西,它意味着你可以为每个文件使用using语句,而不是必须手动处理所有关闭。

它确实意味着一次在内存中包含所有数据库元数据条目,但可能每个元数据条目都小于您需要在内存最终需要的结果数据,以便将结果显示给用户。

即使您将多次查看数据库元数据条目,也会发生在内存中。与文件系统或数据库的IO相比,它应该是无关紧要的。

另一种方法是在读取时按文件名对元数据条目进行分组,将索引保留为元数据条目的一部分。