Как вы ищете большой текстовый файл для строки, не проходя строка за строкой в ​​C #? - PullRequest
13 голосов
/ 19 января 2010

У меня большой текстовый файл, который мне нужен для поиска определенной строки. Есть ли быстрый способ сделать это без чтения построчно?

Этот метод очень медленный из-за размера файлов (более 100 МБ).

Ответы [ 14 ]

7 голосов
/ 19 января 2010

Учитывая размер файлов, вы действительно хотели бы заранее прочитать их полностью в память? Строка за строкой, вероятно, будет лучшим подходом здесь.

4 голосов
/ 20 января 2010

Вот мое решение, которое использует поток для чтения по одному символу за раз.Я создал собственный класс для поиска значения по одному символу за раз, пока не будет найдено все значение.

Я провел несколько тестов с файлом размером 100 МБ, сохраненным на сетевом диске, и скорость полностью зависела от того, насколько быстроэто может прочитать в файле.Если файл был буферизован в 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();
    }
}
2 голосов
/ 20 января 2010

Самый быстрый метод поиска - это алгоритм Бойера-Мура . Этот метод не требует чтения всех байтов из файлов, но требует произвольного доступа к байтам. Также этот метод прост в реализации.

2 голосов
/ 19 января 2010

Во всех случаях вам придется просмотреть все файлы.

Поиск Поиск строки Рабина-Карпа или аналогичный.

1 голос
/ 25 сентября 2014

Вот простое однофункциональное решение, читающее символ за символом.У меня сработало нормально.

/// <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;
}

Надеюсь, это поможет.

1 голос
/ 05 апреля 2013

Как уже сказал Уэйн Корниш: чтение строки за строкой может быть лучшим подходом.

Если вы прочитаете, например, весь файл в строку, а затем выполните поиск по регулярному выражению, это может быть более элегантным, но вы создадите большой строковый объект.

Объекты такого типа могут вызывать проблемы, поскольку они будут храниться в куче больших объектов (LOH, для объектов размером более 85 000 байт). Если вы анализируете многие из этих больших файлов, и ваша память ограничена (x86), вы, скорее всего, столкнетесь с проблемами фрагментации LOH.

=> Лучше читать построчно, если вы анализируете много больших файлов!

1 голос
/ 19 января 2010

Требуется ли вашему проекту каждый раз искать в разных файлах одну и ту же или разную строку или каждый раз искать в разных файлах разные строки?

Если это последнее, вы можете создать индекс файла. Но нет смысла делать это, если файл часто меняется, потому что создание индекса будет дорогим.

Чтобы проиндексировать файл для полнотекстового поиска, вы можете использовать библиотеку Lucene.NET.

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

1 голос
/ 19 января 2010

Вы должны быть в состоянии прочитать символ файла по символу, совпадающему с каждым символом в строке поиска, пока не достигнете конца строки поиска, и в этом случае у вас есть совпадение. Если в какой-то момент прочитанный вами символ не соответствует искомому символу, сбросьте счетчик совпадений до 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 или другого буферизованного читателя.

1 голос
/ 19 января 2010

Вы можете буферизовать большое количество данных из файла в память за один раз, вплоть до любого желаемого ограничения, а затем найти его в строке.

Это приведет к уменьшению числа операций чтения файла и, вероятно, будет более быстрым методом, но это приведет к увеличению объема памяти, если вы установите слишком большой размер буфера.

0 голосов
/ 19 января 2010

Вставьте его в SQL Server 2005/2008 и используйте его возможность полнотекстового поиска.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...