Подтвердить что ты не робот

Применить регулярное выражение в потоке?

Я ищу быстрый и безопасный способ применения регулярных выражений в потоках.

Я нашел несколько примеров в Интернете, говорящих о преобразовании каждого буфера в String, а затем применил Regex в строке.

Этот подход имеет две проблемы:

  • Производительность: преобразование в строки и GC'ing строк - это трата времени и процессора, и этого можно избежать, если бы был более естественный способ применения Regex в потоках.
  • Pure Regex support: Regex pattern иногда может совпадать только при объединении двух буферов вместе (буфер 1 заканчивается первой частью совпадения, а буфер 2 начинается со второй части совпадения). Метод convert-to-string не может обрабатывать этот тип соответствия изначально, я должен предоставить больше информации, такой как максимальная длина, с которой может сравниться шаблон, это не поддерживает знаки + и * regex вообще и никогда не будет поддерживать (неограниченное совпадение длина).

Итак, способ конвертирования в строку не выполняется быстро и не поддерживает полностью Regex.

Есть ли способ/библиотека, который можно использовать для применения Regex в потоках без преобразования в строки и с полной поддержкой Regex?

4b9b3361

Ответ 1

Недавно Intel открыла библиотеку hyperscan под лицензией BSD. Это высокопроизводительный механизм обратного отслеживания на основе NFA.

Особенности: возможность работы с потоками входных данных и одновременное сопоставление нескольких шаблонов. Последний отличается от подхода (pattern1|pattern2|...), он фактически сопоставляет шаблоны одновременно.

В нем также используются наборы инструкций Intel SIMD, такие как SSE4.2, AVX2 и BMI. Резюме проекта и объяснения работы можно найти здесь здесь. Он также обладает отличным разработчиком справочным руководством с большим количеством объяснений, а также соображениями производительности и использования. Маленькая статья об использовании этого в дикой природе.

Ответ 3

Кажется, что вы знаете начальные и конечные разделители совпадений, которые вы пытаетесь получить, правильно? (т.е. [,] или START, END и т.д.). Было бы разумно искать эти разделители, когда поступают данные из вашего потока, а затем создает подстроку между разделителями и выполняет дальнейшую обработку на них?

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

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

Ответ 4

Вы можете добавить дополнительный метод для StreamReader (исходный код, например, Mono, можно использовать для этой цели):

    private StringBuilder lineBuilder;
    public int RegexBufferSize
    {
        set { lastRegexMatchedLength = value; }
        get { return lastRegexMatchedLength; }
    }
    private int lastRegexMatchedLength = 0;

    public virtual string ReadRegex(Regex regex)
    {
        if (base_stream == null)
            throw new ObjectDisposedException("StreamReader", "Cannot read from a closed RegexStreamReader");

        if (pos >= decoded_count && ReadBuffer() == 0)
            return null; // EOF Reached

        if (lineBuilder == null)
            lineBuilder = new StringBuilder();
        else
            lineBuilder.Length = 0;

        lineBuilder.Append(decoded_buffer, pos, decoded_count - pos);
        int bytesRead = ReadBuffer();

        bool dataTested = false;
        while (bytesRead > 0)
        {
            var lineBuilderStartLen = lineBuilder.Length;
            dataTested = false;
            lineBuilder.Append(decoded_buffer, 0, bytesRead);

            if (lineBuilder.Length >= lastRegexMatchedLength)
            {
                var currentBuf = lineBuilder.ToString();
                var match = regex.Match(currentBuf, 0, currentBuf.Length);
                if (match.Success)
                {
                    var offset = match.Index + match.Length;
                    pos = 0;
                    decoded_count = lineBuilder.Length - offset;
                    ensureMinDecodedBufLen(decoded_count);
                    lineBuilder.CopyTo(offset, decoded_buffer, 0, decoded_count);
                    var matchedString = currentBuf.Substring(match.Index, match.Length);
                    return matchedString;
                }
                else
                {
                    lastRegexMatchedLength *= (int) 1.1; // allow for more space before attempting to match
                    dataTested = true;
                }
            }

            bytesRead = ReadBuffer();
        }

        // EOF reached

        if (!dataTested)
        {
            var currentBuf = lineBuilder.ToString();
            var match = regex.Match(currentBuf, 0, currentBuf.Length);
            if (match.Success)
            {
                var offset = match.Index + match.Length;
                pos = 0;
                decoded_count = lineBuilder.Length - offset;
                ensureMinDecodedBufLen(decoded_count);
                lineBuilder.CopyTo(offset, decoded_buffer, 0, decoded_count);
                var matchedString = currentBuf.Substring(match.Index, match.Length);
                return matchedString;

            }
        }
        pos = decoded_count;

        return null;
    }

В приведенном выше методе используются следующие вары:

  • decoded_buffer: буфер char, содержащий/будет содержать прочитанные данные
  • pos: смещение внутри массива, содержащего необработанные данные
  • decoded_count: последний элемент в буфере, содержащий считанные данные
  • RegexBufferSize: минимальный размер ввода регулярного выражения до того, как произойдет соответствующее сопоставление.

Метод ReadBuffer() должен читать данные из потока. Метод makeMinDecodedBufLen() должен убедиться, что decoded_buffer достаточно велик.

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