【问题标题】:Fastest way to search text in multiple files?在多个文件中搜索文本的最快方法?
【发布时间】:2013-09-04 20:47:55
【问题描述】:

我需要在大约 120 个文本文件中找到一些文本,我想知道哪种方法是搜索文本的最佳和最快方式。我应该读取 RichTextBox 中的每个文件,然后使用它的方法来搜索文本,还是应该将这些文件读入字符串变量,然后使用正则表达式进行搜索?

我认为性能背后的主要因素是找到一种方法,这样就不需要循环遍历已经过匹配测试的行。有没有办法一次性找到文件中的所有匹配项?有谁知道像 Visual Studio 那样在文本文件中查找匹配项的方法?它在大约 800-1000 毫秒内搜索了 200 个文本文件以进行匹配。我认为它利用多个线程来完成这一点。

【问题讨论】:

  • 无需将每个文件加载到 RichTextBox 中,您只需加载字符串对象并使用它即可。
  • 搜索什么?没有上下文是不可能回答的。 RichTextBox 在这里无关紧要。
  • @SriramSakthivel ,假设我有 120 个代码文件,我想在这些文件中搜索 public 并重新获取它们的位置?
  • 文件大吗?你想做什么样的搜索:完全匹配、包含、模式?您是想为每次搜索一次又一次地读取文件还是建立一个单词索引以进行多次搜索?

标签: c# regex string richtextbox


【解决方案1】:

根据您的描述(120 个文件,70K-80K 字,每个文件 1-2 MB),似乎最好的方法是读取文件一次并建立一个可以搜索的索引。我在下面提供了一个示例来说明如何完成这样的事情,但是如果您需要比查找精确词或前缀词更复杂的搜索词匹配,那么这对您的用处可能有限。

如果您需要更复杂的文本搜索匹配(同时获得良好的性能),我建议您查看专门为此目的构建的优秀 Lucene 库。

public struct WordLocation
{
    public WordLocation(string fileName, int lineNumber, int wordIndex)
    {
        FileName = fileName;
        LineNumber = lineNumber;
        WordIndex = wordIndex;
    }
    public readonly string FileName; // file containing the word.
    public readonly int LineNumber;  // line within the file.
    public readonly int WordIndex;   // index within the line.
}

public struct WordOccurrences
{
    private WordOccurrences(int nOccurrences, WordLocation[] locations)
    {
        NumberOfOccurrences = nOccurrences;
        Locations = locations;
    }

    public static readonly WordOccurrences None = new WordOccurrences(0, new WordLocation[0]);

    public static WordOccurrences FirstOccurrence(string fileName, int lineNumber, int wordIndex)
    {
        return new WordOccurrences(1, new [] { new WordLocation(fileName, lineNumber, wordIndex) });
    }

    public WordOccurances AddOccurrence(string fileName, int lineNumber, int wordIndex)
    {
        return new WordOccurrences(
            NumberOfOccurrences + 1, 
            Locations
                .Concat(
                    new [] { new WordLocation(fileName, lineNumber, wordIndex) })
                .ToArray());
    }

    public readonly int NumberOfOccurrences;
    public readonly WordLocation[] Locations;
}

public interface IWordIndexBuilder
{
    void AddWordOccurrence(string word, string fileName, int lineNumber, int wordIndex);
    IWordIndex Build();
}

public interface IWordIndex
{
    WordOccurrences Find(string word);
}

public static class BuilderExtensions
{
    public static IWordIndex BuildIndexFromFiles(this IWordIndexBuilder builder, IEnumerable<FileInfo> wordFiles)
    {
        var wordSeparators = new char[] {',', ' ', '\t', ';' /* etc */ };
        foreach (var file in wordFiles)
        {
            var lineNumber = 1;
            using (var reader = file.OpenText())
            {
                while (!reader.EndOfStream)
                {
                    var words = reader
                         .ReadLine() 
                         .Split(wordSeparators, StringSplitOptions.RemoveEmptyEntries)
                         .Select(f => f.Trim());

                    var wordIndex = 1;
                    foreach (var word in words)
                        builder.AddWordOccurrence(word, file.FullName, lineNumber, wordIndex++);

                    lineNumber++;
                }
            }
        }
        return builder.Build();
    }
}

那么最简单的索引实现(只能进行精确匹配查找)在内部使用字典:

public class DictionaryIndexBuilder : IIndexBuilder
{
    private Dictionary<string, WordOccurrences> _dict;

    private class DictionaryIndex : IWordIndex 
    {
        private readonly Dictionary<string, WordOccurrences> _dict;

        public DictionaryIndex(Dictionary<string, WordOccurrences> dict)
        {
            _dict = dict;
        }
        public WordOccurrences Find(string word)
        {
           WordOccurrences found;
           if (_dict.TryGetValue(word, out found);
               return found;
           return WordOccurrences.None;
        }
    }

    public DictionaryIndexBuilder(IEqualityComparer<string> comparer)
    {
        _dict = new Dictionary<string, WordOccurrences>(comparer);
    }
    public void AddWordOccurrence(string word, string fileName, int lineNumber, int wordIndex)
    {
        WordOccurrences current;
        if (!_dict.TryGetValue(word, out current))
            _dict[word] = WordOccurrences.FirstOccurrence(fileName, lineNumber, wordIndex);
        else
            _dict[word] = current.AddOccurrence(fileName, lineNumber, wordIndex);
    }
    public IWordIndex Build()
    {
        var dict = _dict;
        _dict = null;
        return new DictionaryIndex(dict);
    }
}

用法:

var builder = new DictionaryIndexBuilder(EqualityComparer<string>.Default);
var index = builder.BuildIndexFromFiles(myListOfFiles);
var matchSocks = index.Find("Socks");

如果您还想进行前缀查找,请实现一个使用排序字典的索引构建器/索引类(并更改 IWordIndex.Find 方法以返回多个匹配项,或者在接口中添加一个新方法以查找部分/模式火柴)。

如果您想进行更复杂的查找,请选择 Lucence。

【讨论】:

    【解决方案2】:

    如果我在你那里,我会怎么做:

    1- 我会将所有文件路径加载到字符串列表中。

    2- 我将创建一个新列表来存储与我的搜索词匹配的文件路径。

    3- 我将在文件列表中循环 foreach 并搜索我的术语,然后将匹配的文件添加到新列表中。

    string searchTerm = "Some terms";
        string[] MyFilesList = Directory.GetFiles(@"c:\txtDirPath\", "*.txt");
        List<string> FoundedSearch=new List<string>();
        foreach (string filename in MyFilesList)
        {
            string textFile = File.ReadAllText(filename);
            if (textFile.Contains(searchTerm))
            {
                FoundedSearch.Add(filename);
            }
        }
    

    那么您可以随心所欲地处理 List:FoundedSearch。

    顺便说一句:

    我不知道最佳答案,但性能会非常好,直到 800 个文本文件,每个文件 1000 个字 您可以使用this chart 找到相当不错的性能

    【讨论】:

      【解决方案3】:

      我假设您需要在每个文件中搜索相同的字符串。您可以为每次搜索使用compiled regex

      string searchTerm = "searchWord";
      Regex rx = new Regex(String.Format("\b{0}\b", searchTerm), RegexOptions.Compiled);
      List<string> filePaths = new List<string>();
      
      foreach (string filePath in filePaths)
      {
         string allText = File.ReadAllText(filePath);
         var matches = rx.Matches(allText);             
         //rest of code
      }
      

      您必须对性能进行基准测试,但我认为主要瓶颈将是从磁盘打开和读取文件。如果事实证明是这样,您可以查看Memory-Mapped Files。或者,根据您最终想要做什么,专用的文本搜索器,例如 Lucene.Net(如 cmets 中提到的 I4V)可能更合适。

      【讨论】:

        猜你喜欢
        • 2013-07-05
        • 1970-01-01
        • 2017-12-31
        • 1970-01-01
        • 2012-02-03
        • 2016-10-25
        • 2010-11-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多