.NET Регулярные выражения в байтах вместо символов

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

Ввод представляет собой массив (или перечисление) байтов.

Я не хочу преобразовывать байты в символы по следующим причинам:

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

Поэтому я не могу использовать Regex.

Единственное решение, которое я знаю, использует Boost.Regex (который работает с байтами - C символов), но это библиотека С++, которая обертка с использованием С++/CLI займет значительную работу.

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

Спасибо.

Ответы

Ответ 1

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

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

Пример:

//BLING
byte[] inputBuffer = { 66, 76, 73, 78, 71 };

string stringBuffer = new string('\0', 1000);

Regex regex = new Regex("ING", RegexOptions.Compiled);

unsafe
{
    fixed (char* charArray = stringBuffer)
    {
        byte* buffer = (byte*)(charArray);

        //Hard-coded example of string mutation, in practice you would
        //loop over your input buffers and regex\match so that the string
        //buffer is re-used.

        buffer[0] = inputBuffer[0];
        buffer[2] = inputBuffer[1];
        buffer[4] = inputBuffer[2];
        buffer[6] = inputBuffer[3];
        buffer[8] = inputBuffer[4];

        Console.WriteLine("Mutated string:'{0}'.",
             stringBuffer.Substring(0, inputBuffer.Length));

        Match match = regex.Match(stringBuffer, 0, inputBuffer.Length);

        Console.WriteLine("Position:{0} Length:{1}.", match.Index, match.Length);
    }
}

Используя этот метод, вы можете выделить строку "buffer", которая может быть повторно использована как вход в Regex, но вы можете мутировать ее с байтами каждый раз. Это позволяет избежать накладных расходов на преобразование\кодирование вашего байтового массива в новую строку .Net каждый раз, когда вы хотите выполнить сопоставление. Это может показаться очень значительным, поскольку я видел много алгоритмов в .Net, пытающихся идти на миллион миль в час, только чтобы быть поставленными на колени с помощью генерации струн и последующего спама в куче и времени, проведенного в GC.

Очевидно, что это небезопасный код, но это .Net.

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

Обновление

Собственно, после разбора Regex\Match\Group\Capture, похоже, что он генерирует только захваченную строку при доступе к свойству Value, поэтому вы можете, по крайней мере, не генерировать строки, если имеете доступ только к свойствам индекса и длины. Тем не менее, вы будете генерировать все поддерживающие объекты Regex.

Ответ 2

Хорошо, если бы я столкнулся с этой проблемой, я бы сделал оболочку С++/CLI, за исключением того, что создал бы специализированный код для того, чего хочу достичь. В конце концов создайте обертку со временем, чтобы делать общие вещи, но это просто вариант.

Первым шагом является объединение только ввода и вывода Boost:: Regex. Создавайте специализированные функции на С++, которые делают все, что вам нужно, и используйте CLI, чтобы передать входные данные в код С++, а затем вернуть результат с помощью CLI. Это не выглядит для меня слишком большой работой.

Update:

Позвольте мне попытаться прояснить мою мысль. Несмотря на то, что я могу ошибаться, я считаю, что вы не сможете найти реализацию .NET Binary Regex, которую вы могли бы использовать. Вот почему - нравится вам это или нет - вам придется выбирать между оболочкой CLI и преобразованием байтов в символы для использования .NET Regex. По-моему, обертка - лучший выбор, потому что она будет работать быстрее. Я не проводил бенчмаркинга, это просто предположение, основанное на:

  • Используя обертку, вам просто нужно нажать тип указателя (bytes ↔ chars).
  • Используя .NET Regex, вы должны   преобразовать каждый байт ввода.

Ответ 3

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

static bool Evaluate(byte[] data, byte[] sequence, int dataIndex=0, int sequenceIndex=0)
{
       if (sequence[sequenceIndex] == data[dataIndex])
       {
           if (sequenceIndex == sequence.Length - 1)
               return true;
           else if (dataIndex == data.Length - 1)
               return false;
           else
               return Evaluate(data, sequence, dataIndex + 1, sequenceIndex + 1);
       }
       else
       {
           if (dataIndex < data.Length - 1)
               return Evaluate(data, sequence, dataIndex+1, 0);
           else
               return false;
       }
}

Вы можете повысить эффективность несколькими способами (т.е. искать совпадение первого байта вместо итерации и т.д.), но это может заставить вас начать... надеюсь, что это поможет.

Ответ 4

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

bool ParseUDSResponse(PassThruMsg rxMsg, UDScmd.Mode txMode, byte txSubFunction, out UDScmd.Response functionResponse, out byte[] payload)
{
    payload = new byte[0];
    functionResponse = UDScmd.Response.UNKNOWN;
    bool positiveReponse = false;
    var rxMsgBytes = rxMsg.GetBytes();

    //Iterate the reply bytes to find the echod ECU index, response code, function response and payload data if there is any
    //If we could use some kind of HEX regex this would be a bit neater
    //Iterate until we get past any and all null padding
    int stateMachine = 0;
    for (int i = 0; i < rxMsgBytes.Length; i++)
    {
        switch (stateMachine)
        {
            case 0:
                if (rxMsgBytes[i] == 0x07) stateMachine = 1;
                break;
            case 1:
                if (rxMsgBytes[i] == 0xE8) stateMachine = 2;
                else return false;
            case 2:
                if (rxMsgBytes[i] == (byte)txMode + (byte)OBDcmd.Reponse.SUCCESS)
                {
                    //Positive response to the requested mode
                    positiveReponse = true;
                }
                else if(rxMsgBytes[i] != (byte)OBDcmd.Reponse.NEGATIVE_RESPONSE)
                {
                    //This is an invalid response, give up now
                    return false;
                }
                stateMachine = 3;
                break;
            case 3:
                functionResponse = (UDScmd.Response)rxMsgBytes[i];
                if (positiveReponse && rxMsgBytes[i] == txSubFunction)
                {
                    //We have a positive response and a positive subfunction code (subfunction is reflected)
                    int payloadLength = rxMsgBytes.Length - i;
                    if(payloadLength > 0)
                    {
                        payload = new byte[payloadLength];
                        Array.Copy(rxMsgBytes, i, payload, 0, payloadLength);
                    }
                    return true;
                } else
                {
                    //We had a positive response but a negative subfunction error
                    //we return the function error code so it can be relayed
                    return false;
                }
            default:
                return false;
        }
    }
    return false;
}