Как искать большой текстовый файл для строки, не переходя по строкам в С#?

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

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

Ответы

Ответ 1

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

Ответ 2

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

Я провел несколько тестов с 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();
    }
}

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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

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

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

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

Ответ 8

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

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

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

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

Ответ 9

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

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

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

Ответ 10

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

Ответ 11

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

Единственным способом избежать поиска по всему файлу является сортировка или организация ввода заранее. Например, если это файл XML, и вам нужно выполнить многие из этих поисков, было бы целесообразно проанализировать XML файл в дереве DOM. Или, если это список слов, и вы ищете все слова, начинающиеся с букв "aero", может иметь смысл сначала отсортировать весь вход, если вы делаете много такого поиска в одном файле.

Ответ 12

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

Очевидно, что если часть строки, которая будет найдена, находится в конце файла, прирост производительности не будет.

Ответ 13

Если вы ищете только определенную строку, я бы сказал, что построчно-лайн-это лучший и самый эффективный механизм. С другой стороны, если вы собираетесь искать несколько строк, особенно в нескольких разных точках приложения, вы можете посмотреть в Lucene.Net, чтобы создать индекс, а затем запросить индекс. Если это одноразовый прогон (т.е. Вам больше не нужно будет запрашивать один и тот же файл позже), вы можете создать индекс во временном файле, который будет автоматически очищаться системой (обычно время загрузки, или вы может удалить его самостоятельно, когда ваша программа выйдет). Если вам нужно снова искать один и тот же файл позже, вы можете сохранить индекс в известном месте и получить гораздо лучшую производительность во второй раз.

Ответ 14

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