Есть ли быстрый способ разбора большого файла с регулярным выражением?

Проблема: Очень большой, большой файл Мне нужно разбирать строки за строкой, чтобы получить 3 значения из каждой строки. Все работает, но для анализа всего файла требуется много времени. Можно ли сделать это за считанные секунды? Типичное время его приема составляет от 1 минуты до 2 минут.

Размер файла примера 148,208KB

Я использую регулярное выражение для синтаксического анализа каждой строки:

Вот мой код С#:

private static void ReadTheLines(int max, Responder rp, string inputFile)
{
    List<int> rate = new List<int>();
    double counter = 1;
    try
    {
        using (var sr = new StreamReader(inputFile, Encoding.UTF8, true, 1024))
        {
            string line;
            Console.WriteLine("Reading....");
            while ((line = sr.ReadLine()) != null)
            {
                if (counter <= max)
                {
                    counter++;
                    rate = rp.GetRateLine(line);
                }
                else if (max == 0)
                {
                    counter++;
                    rate = rp.GetRateLine(line);
                }
            }
            rp.GetRate(rate);
            Console.ReadLine();
        }
    }
    catch (Exception e)
    {
        Console.WriteLine("The file could not be read:");
        Console.WriteLine(e.Message);
    }
}

Вот мое регулярное выражение:

public List<int> GetRateLine(string justALine)
{
    const string reg = @"^\d{1,}.+\[(.*)\s[\-]\d{1,}].+GET.*HTTP.*\d{3}[\s](\d{1,})[\s](\d{1,})$";
    Match match = Regex.Match(justALine, reg,
                                RegexOptions.IgnoreCase);

    // Here we check the Match instance.
    if (match.Success)
    {
        // Finally, we get the Group value and display it.

        string theRate = match.Groups[3].Value;
        Ratestorage.Add(Convert.ToInt32(theRate));
    }
    else
    {
        Ratestorage.Add(0);
    }
    return Ratestorage;
}

Вот пример строки для разбора, обычно около 200 000 строк:

10.10.10.10 - - [27/ноябрь/2002: 16: 46: 20 -0500] "GET/solr/HTTP/1.1" 200 4926 789

Ответ 1

Файлы с памятью и Задача параллельной библиотеки для справки.

  • Создать постоянный MMF с несколькими видами произвольного доступа. Каждый вид соответствует определенной части файла
  • Определите метод синтаксического анализа с параметром типа IEnumerable<string>, в основном для абстрактного набора не проанализированных строк
  • Создайте и запустите одну задачу TPL в одном представлении MMF с помощью Parse(IEnumerable<string>) в качестве действия задачи
  • Каждый из рабочих задач добавляет анализируемые данные в общую очередь BlockingCollection
  • Другая задача прослушивает BC (GetConsumingEnumerable()) и обрабатывает все данные, которые уже обрабатываются рабочими задачами

См. шаблон трубопроводов в MSDN

Надо сказать, что это решение для .NET Framework >=4

Ответ 2

В настоящий момент вы воссоздаете Regex каждый раз при вызове GetRateLine, который возникает каждый раз, когда вы читаете строку.

Если вы создаете экземпляр Regex один раз заранее, а затем используйте нестатический Match, вы сэкономите время на компиляцию регулярных выражений, что потенциально может дать вам прирост скорости.

Говоря, это, скорее всего, не займет у вас минуты от нескольких секунд...

Ответ 3

Вместо повторного создания регулярного выражения для каждого вызова GetRateLine создайте его заранее, передав RegexOptions.Compiled вариант Regex(String,RegexOptions) конструктор.

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

Ответ 4

С кратким взглядом я бы попробовал несколько вещей...

Во-первых, увеличьте свой буфер потока файлов до 64kb:

using (var sr = new StreamReader(inputFile, Encoding.UTF8, true, 65536))

Во-вторых, создайте Regex один раз вместо использования строки внутри цикла:

static readonly Regex rateExpression = new Regex(@"^\d{1,}.+\[(.*)\s[\-]\d{1,}].+GET.*HTTP.*\d{3}[\s](\d{1,})[\s](\d{1,})$", RegexOptions.IgnoreCase);
//In GetRateLine() change to:
Match match = rateExpression.Match(justALine);

В-третьих, используйте экземпляр одного экземпляра, если Responder.GetRate() возвращает список или массив.

// replace: 'rp.GetRate(rate)', with:
rate = rp.GetRate();

Я бы перераспределял список на "разумный" предел:

List<int> rate = new List<int>(10000);

Вы также можете рассмотреть возможность изменения кодировки с UTF-8 на ASCII, если она доступна и применима к вашим конкретным потребностям.

Комментарии

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

Если вам нужен пример синтаксического анализа на стороне ответа на этот вопрос