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