在每个C#的大小为150 MB的多个文本文件中搜索字符串

时间:2012-11-26 18:02:34

标签: c# copy

我有多个 150MB 大小的.txt文件。使用C#我需要从每个文件中检索包含字符串模式的所有行,然后将这些行写入新创建的文件。

我已经研究过类似的问题,但他们建议的答案都没有给我提供最快的结果。我尝试了正则表达式,linq查询,包含方法,使用字节数组进行搜索,但所有这些都需要超过30分钟来读取和比较文件内容。

我的测试文件没有任何特定的格式,就像基于数据视图的基于demiliter和过滤器的原始数据一样。下面是该文件中每一行的样本格式。

Sample.txt的

LTYY;;0,0,;123456789;;;;;;;20121002 02:00;;
ptgh;;0,0,;123456789;;;;;;;20121002 02:00;;
HYTF;;0,0,;846234863;;;;;;;20121002 02:00;;
Multiple records......

我的代码

using (StreamWriter SW = new StreamWriter(newFile))
            {
                using(StreamReader sr = new StreamReader(sourceFilePath))
                {
                while (sr.Peek() >= 0) 
                {
                   if (sr.ReadLine().Contains(stringToSearch))
                     SW.WriteLine(sr.ReadLine().ToString());
                 }
}
}

我想要一个示例代码,从Sample.txt搜索 123456789 只需不到一分钟的时间。如果我的要求不明确,请告诉我。提前谢谢!

修改

我找到了根本原因,因为驻留在远程服务器中的文件占用了更多的时间来读取它们,因为当我将文件复制到本地计算机时,所有比较方法都很快完成,所以这不是问题所在我们阅读或比较内容的方式,或多或少花了相同的时间。

但是现在我如何解决这个问题,我无法将所有这些文件复制到我的机器上进行比较并获得OutOfMemory异常

7 个答案:

答案 0 :(得分:3)

最快的搜索方法是使用Boyer–Moore string search algorithm,因为此方法不需要读取文件中的所有字节,但需要随机访问字节,您可以尝试使用{{3} }}

或者您可以尝试从Rabin Karp Algorithm

执行类似以下代码的操作
  public static int FindInFile(string fileName, string value)
  {   // returns complement of number of characters in file if not found
    // else returns index where value found
  int index = 0;
   using (System.IO.StreamReader reader = new System.IO.StreamReader(fileName))
   {
    if (String.IsNullOrEmpty(value))
        return 0;
    StringSearch valueSearch = new StringSearch(value);
    int readChar;
    while ((readChar = reader.Read()) >= 0)
    {
        ++index;
        if (valueSearch.Found(readChar))
            return index - value.Length;
    }
}
return ~index;
}
 public class StringSearch
 {   // Call Found one character at a time until string found
private readonly string value;
private readonly List<int> indexList = new List<int>();
public StringSearch(string value)
{
    this.value = value;
}
public bool Found(int nextChar)
{
    for (int index = 0; index < indexList.Count; )
    {
        int valueIndex = indexList[index];
        if (value[valueIndex] == nextChar)
        {
            ++valueIndex;
            if (valueIndex == value.Length)
            {
                indexList[index] = indexList[indexList.Count - 1];
                indexList.RemoveAt(indexList.Count - 1);
                return true;
            }
            else
            {
                indexList[index] = valueIndex;
                ++index;
            }
        }
        else
        {   // next char does not match
            indexList[index] = indexList[indexList.Count - 1];
            indexList.RemoveAt(indexList.Count - 1);
        }
    }
    if (value[0] == nextChar)
    {
        if (value.Length == 1)
            return true;
        indexList.Add(1);
    }
    return false;
}
public void Reset()
{
    indexList.Clear();
}
}

答案 1 :(得分:1)

我不知道这需要多长时间才能运行,但这里有一些改进:

using (StreamWriter SW = new StreamWriter(newFile))
{
    using (StreamReader sr = new StreamReader(sourceFilePath))
    {
        while (!sr.EndOfStream)
        {
            var line = sr.ReadLine();
            if (line.Contains(stringToSearch))
                SW.WriteLine(line);
        }
    }
}

请注意,您不需要PeekEndOfStream会为您提供所需内容。你打电话给ReadLine两次(可能不是你想要的)。而且无需在ToString()上致电string

答案 2 :(得分:1)

150MB是150MB。如果你有一个线程通过整个150MB,逐行(“换行”由换行符/组或EOF终止),你的进程必须读入并旋转所有150MB的数据(不是全部在一次,它不必同时保持所有这一切)。通过157,286,400个字符进行线性搜索非常简单,需要花费时间,而且你说你有很多这样的文件。

第一件事;你正在读两次流出来的那条线。在大多数情况下,这实际上会导致您在匹配时读取两行;写入新文件的内容将是包含搜索字符串的行之后的行。这可能不是你想要的(然后可能是)。如果要编写实际包含搜索字符串的行,请在执行包含检查之前将其读入变量。

其次,String.Contains()必然会执行线性搜索。在你的情况下,行为实际上将接近N ^ 2,因为当在字符串中搜索字符串时,必须找到第一个字符,然后在每个字符的位置逐个匹配后续字符,直到所有字符为止。搜索字符串匹配或找到不匹配的字符;当发生不匹配时,算法必须在初始匹配后返回到字符以避免跳过可能的匹配,这意味着当检查长字符串与具有许多部分匹配的较长字符串时,它可以多次测试相同的字符。因此,该策略在技术上是一种“强力”解决方案。不幸的是,当你不知道在哪里看时(例如在未分类的数据文件中),没有更有效的解决方案。

除了能够对文件数据进行排序然后执行索引搜索之外,我能建议的唯一可能的加速是多线程解决方案;如果您只在一个查看每个文件的线程上运行此方法,那么不仅只有一个线程在执行该作业,而且该线程始终在等待硬盘驱动器提供所需的数据。每次处理一个文件的5个或10个线程不仅可以更有效地利用现代多核CPU的真正功能,而且当一个线程在硬盘上等待时,另一个已加载数据的线程可以执行,进一步提高了这种方法的效率。请记住,数据来自CPU的距离越远,CPU获取数据所需的时间越长,当CPU每秒可以处理20到40亿件事情时,硬盘驱动器需要等待几毫秒才能实现你每秒失去了数以百万计的潜在指令。

答案 3 :(得分:1)

正如我已经说过的那样,你应该拥有一个数据库,但无论如何。

最快,最短,最好的方式(即使是单行)是:

File.AppendAllLines("b.txt", File.ReadLines("a.txt")
                                 .Where(x => x.Contains("123456789")));

但速度快? 150MB是150MB。这需要一段时间。 您可以将Contains方法替换为您自己的方法,以便更快地进行比较,但这是一个完全不同的问题。

其他可能的解决方案......

var sb = new StringBuilder();

foreach (var x in File.ReadLines("a.txt").Where(x => x.Contains("123456789")))
{
    sb.AppendLine(x);
}

File.WriteAllText("b.txt", sb.ToString()); // That is one heavy operation there...

使用150MB的文件对其进行测试,并在3秒内找到所有结果。花费时间的事情是将结果写入第二个文件(如果有很多结果)。

答案 4 :(得分:0)

我没有给你提供示例代码,但您是否尝试过对文件内容进行排序?

尝试从150MB的文件中搜索一个字符串将花费一些时间进行切片,如果正则表达式花费的时间太长,那么我建议可能会对文件的内容进行排序,以便在实际搜索之前,你大致知道"123456789"会发生什么,这样你就不必搜索不重要的部分。

答案 5 :(得分:0)

不要同时读写。首先搜索,保存匹配行的列表,并在结尾处将其写入文件。

using System;
using System.Collections.Generic;
using System.IO;
...
List<string> list = new List<string>();
using (StreamReader reader = new StreamReader("input.txt")) {
  string line;
  while ((line = reader.ReadLine()) != null) {
    if (line.Contains(stringToSearch)) {
      list.Add(line); // Add to list.
    }
  }
}
using (StreamWriter writer = new StreamWriter("output.txt")) {
  foreach (string line in list) {
    writer.WriteLine(line);
  }
}

答案 6 :(得分:0)

在进行字符串比较时,您将在阻止这些文件输入的方法中遇到性能问题。

但Windows有一个非常高性能的GREP工具,用于对名为FINDSTR的文本文件进行字符串搜索,这可能足够快。您可以简单地将其称为shell命令或将命令的结果重定向到输出文件。

预处理(排序)或将大文件加载到数据库中会更快,但我假设您已经拥有需要搜索的现有文件。