【问题标题】:How do you search a large text file for a string without going line by line in C#?如何在 C# 中逐行搜索大文本文件中的字符串?
【发布时间】:2011-01-06 21:58:57
【问题描述】:

我有一个大的文本文件,我需要搜索一个特定的字符串。有没有不逐行阅读的快速方法?

由于文件大小(超过 100 MB),此方法非常慢。

【问题讨论】:

  • 你对你的程序进行了概要分析吗?
  • 这个文件是经常变化还是静态的?如果是静态的,你可以做一个离线算法并索引它,这样你就可以在运行时快速到达文档所需的小节。
  • 我已经看到了很多将文件逐部分读入内存的建议,但是您将如何处理搜索词从一个文件段开始并以另一个文件段结束的情况。可能加载重叠段,如果发生这种情况,下一个块读取应该包含整个术语

标签: c# search text


【解决方案1】:

考虑到文件的大小,您真的希望事先将它们完全读入内存吗?逐行可能是这里最好的方法。

【讨论】:

    【解决方案2】:

    这是我的解决方案,它使用流一次读取一个字符。我创建了一个自定义类来一次搜索一个字符的值,直到找到整个值。

    我对保存在网络驱动器上的 100MB 文件进行了一些测试,速度完全取决于它在文件中读取的速度。如果文件在 Windows 中缓冲,则搜索整个文件的时间不到 3 秒。否则可能需要 7 秒到 60 秒,具体取决于网络速度。

    如果针对内存中的字符串运行并且没有匹配的字符,则搜索本身只需要不到一秒钟的时间。如果找到的很多前导字符都匹配,则搜索可能需要更长的时间。

    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();
        }
    }
    

    【讨论】:

      【解决方案3】:

      在所有情况下,您都必须检查所有文件。

      查找Rabin-Karp string search 或类似的。

      【讨论】:

      • 不一定每次搜索。如果要多次搜索同一个文件,建立该文件的索引可能是有意义的,因此只需要对整个文件进行一次遍历即可启用任意数量的快速查找。
      【解决方案4】:

      最快的搜索方法是Boyer-Moore algorithm。此方法不需要从文件中读取所有字节,但需要随机访问字节。而且这种方法实现简单。

      【讨论】:

        【解决方案5】:

        您的项目是每次都需要在不同的文件中搜索相同或不同的字符串,还是每次都在同一个文件中搜索不同的字符串?

        如果是后者,您可以建立文件的索引。但是如果文件频繁更改,这样做没有意义,因为构建索引会很昂贵。

        要索引文件以进行全文搜索,您可以使用 Lucene.NET 库。

        http://incubator.apache.org/lucene.net/

        【讨论】:

          【解决方案6】:

          您应该能够逐个匹配搜索字符串中的每个字符来读取文件字符,直到您到达搜索字符串的末尾,在这种情况下您有一个匹配项。如果在任何时候您阅读的字符与您要查找的字符不匹配,请将匹配计数重置为 0 并重新开始。例如(****伪代码/未测试****):

          byte[] lookingFor = System.Text.Encoding.UTF8.GetBytes("hello world");
          int index = 0;
          int position = 0;
          bool matchFound = false;
          
          using (FileStream fileStream = new FileStream(fileName, FileMode.Open))
          {
            while (fileStream.ReadByte() == lookingFor[index])
            {
              index++;
          
              if (index == lookingFor.length) 
              {
                 matchFound = true;
                 position = File.position - lookingFor.length;
                 break;
              }
            }
          }
          

          这是您可以使用的众多算法之一(尽管它可能会因长度检查而偏离)。它只会找到第一个匹配项,因此您可能希望将 while 循环包装在另一个循环中以查找多个匹配项。

          另外,关于逐行读取文件需要注意的一点是,如果要匹配的所需字符串跨越行,您将找不到它。如果没问题,那么您可以逐行搜索,但如果您需要搜索字符串以跨越行,您将需要使用我上面详述的算法。

          最后,如果您正在寻找最佳速度(听起来就是这样),您需要迁移上面的代码以使用 StreamReader 或其他一些缓冲阅读器。

          【讨论】:

            【解决方案7】:

            正如 Wayne Cornish 已经说过的:逐行阅读可能是最好的方法。

            例如,如果您将整个文件读入一个字符串,然后使用正则表达式进行搜索,它可能会更优雅,但您会创建一个大字符串对象。

            这类对象可能会导致问题,因为它们将存储在大对象堆中(LOH,用于 85.000 字节以上的对象)。如果您解析许多这些大文件并且您的内存有限(x86),您可能会遇到 LOH 碎片问题。

            => 如果您解析许多大文件,最好逐行阅读!

            【讨论】:

              【解决方案8】:

              这是一个简单的逐字符读取的单功能解决方案。对我来说效果很好。

              /// <summary>
              /// Find <paramref name="toFind"/> in <paramref name="reader"/>.
              /// </summary>
              /// <param name="reader">The <see cref="TextReader"/> to find <paramref name="toFind"/> in.</param>
              /// <param name="toFind">The string to find.</param>
              /// <returns>Position within <paramref name="reader"/> where <paramref name="toFind"/> starts or -1 if not found.</returns>
              /// <exception cref="ArgumentNullException">When <paramref name="reader"/> is null.</exception>
              /// <exception cref="ArgumentException">When <paramref name="toFind"/> is null or empty.</exception>
              public int FindString(TextReader reader, string toFind)
              {
                  if(reader == null)
                      throw new ArgumentNullException("reader");
              
                  if(string.IsNullOrEmpty(toFind))
                      throw new ArgumentException("String to find may not be null or empty.");
              
                  int charsRead = -1;
                  int pos = 0;
                  int chr;
              
                  do
                  {
                      charsRead++;
                      chr = reader.Read();
                      pos = chr == toFind[pos] ? pos + 1 : 0;
                  }
                  while(chr >= 0 && pos < toFind.Length);
              
                  int result = chr < 0 ? -1 : charsRead - toFind.Length;
                  return result < 0 ? -1 : result;
              }
              

              希望对您有所帮助。

              【讨论】:

                【解决方案9】:

                您可以一次将文件中的大量数据缓冲到内存中,直至达到您希望的任何约束,然后在其中搜索字符串。

                这会减少对文件的读取次数,并且可能是一种更快的方法,但如果您将缓冲区大小设置得太高,则会更加占用内存。

                【讨论】:

                  【解决方案10】:

                  如果你想加快逐行阅读的速度,你可以创建一个基于队列的应用程序:
                  一个线程读取这些行并将它们排入线程安全队列。然后第二个可以处理字符串

                  【讨论】:

                    【解决方案11】:

                    我有一个大文本文件,我需要搜索一个特定的字符串。有没有不逐行阅读的快速方法?

                    避免搜索整个文件的唯一方法是预先对输入进行排序或组织。例如,如果这是一个 XML 文件,并且您需要执行许多此类搜索,那么将 XML 文件解析为 DOM 树是有意义的。或者,如果这是一个单词列表,并且您正在查找所有以字母“aero”开头的单词,那么如果您在同一个文件上进行大量此类搜索,则首先对整个输入进行排序可能是有意义的.

                    【讨论】:

                      【解决方案12】:

                      如果您只是在寻找特定的字符串,我会说逐行是最好和最有效的机制。另一方面,如果您要查找多个字符串,尤其是在应用程序中的几个不同点,您可能需要查看Lucene.Net 以创建索引,然后查询该索引。如果这是一次性运行(即,您以后不需要再次查询同一个文件),您可以在一个临时文件中创建索引,该文件将由系统自动清理(通常是启动时间;或者您程序退出时可以自己删除)。如果您以后需要再次搜索相同的文件,您可以将索引保存在已知位置并在第二次获得更好的性能。

                      【讨论】:

                        【解决方案13】:

                        将其粘贴到 SQL Server 2005/2008 中并使用其全文搜索功能。

                        【讨论】:

                          【解决方案14】:

                          这里的速度问题很可能是在执行搜索之前将文件加载到内存中的速度。尝试分析您的应用程序以查看瓶颈在哪里。如果它正在加载文件,您可以尝试“分块”文件加载,以便文件以小块流式传输,并且每个块都对其执行搜索。

                          显然,如果要找到的字符串部分位于文件末尾,则不会有性能提升。

                          【讨论】:

                            猜你喜欢
                            • 2010-12-10
                            • 1970-01-01
                            • 2012-10-01
                            • 1970-01-01
                            • 2011-06-23
                            • 1970-01-01
                            • 2014-06-01
                            • 1970-01-01
                            • 2019-10-24
                            相关资源
                            最近更新 更多