如何在不在C#中逐行搜索字符串的大文本文件中搜索?

时间:2010-01-19 17:19:44

标签: c# search text

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

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

14 个答案:

答案 0 :(得分:7)

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

答案 1 :(得分:4)

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

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

如果针对内存中的String运行并且没有匹配的字符,则搜索本身花费的时间不到一秒。如果发现很多主要字符与搜索相匹配可能需要更长的时间。

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

答案 2 :(得分:2)

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

查找Rabin-Karp string search或类似内容。

答案 3 :(得分:2)

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

您应该能够通过匹配搜索字符串中每个字符的字符来读取文件字符,直到您到达搜索字符串的末尾,在这种情况下您将匹配。如果您在任何时候阅读的字符与您要查找的字符不匹配,请将匹配的计数重置为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或其他缓冲读卡器。

答案 6 :(得分:1)

您的项目是否需要每次都搜索不同的文件以查找相同或不同的字符串,或者每次都在同一文件中搜索不同的字符串?

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

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

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

答案 7 :(得分:1)

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

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

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

<强> =&GT;如果您解析许多大文件,请更好地逐行阅读!

答案 8 :(得分:1)

这是一个简单的单功能解决方案,逐字逐句阅读。工作得很好。

/// <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 :(得分:0)

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

答案 10 :(得分:0)

  

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

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

答案 11 :(得分:0)

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

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

答案 12 :(得分:0)

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

答案 13 :(得分:0)

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