Как преобразовать байтовый массив в шестнадцатеричную строку и наоборот?
Как преобразовать байтовый массив в шестнадцатеричную строку и наоборот?
53 ответа
Или:
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
или же:
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
Есть еще больше вариантов сделать это, например, здесь.
Обратное преобразование будет выглядеть так:
public static byte[] StringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
С помощью Substring
это лучший вариант в сочетании с Convert.ToByte
, Смотрите этот ответ для получения дополнительной информации. Если вам нужна лучшая производительность, вы должны избегать Convert.ToByte
прежде чем вы можете уронить SubString
,
Анализ производительности
Примечание: новый лидер по состоянию на 2015-08-20.
Я проверил каждый из различных методов конвертации через некоторые грубые Stopwatch
тестирование производительности, прогон со случайным предложением (n=61, 1000 итераций) и прогон с текстом Project Gutenburg (n=1 238 957, 150 итераций). Вот результаты, примерно от самого быстрого до самого медленного. Все измерения даны в тиках ( 10000 тиков = 1 мс), и все относительные ноты сравниваются с [самыми медленными] StringBuilder
реализация. Используемый код см. Ниже или в репозитории тестового фреймворка, где я сейчас поддерживаю код для запуска этого.
отказ
ВНИМАНИЕ: не полагайтесь на эти характеристики для чего-то конкретного; это просто пример пробных данных. Если вам действительно нужна первоклассная производительность, пожалуйста, протестируйте эти методы в среде, представляющей ваши производственные потребности, с данными, представляющими то, что вы будете использовать.
Результаты
- Поиск по байту
unsafe
(через CodesInChaos) (добавлено в тестовое репо airbreather)- Текст: 4727,85 (105,2X)
- Приговор: 0,28 (99,7X)
- Поиск по байту (через CodesInChaos)
- Текст: 10 853,96 (в 45,8 раза быстрее)
- Приговор: 0,65 (в 42,7 раза быстрее)
- Байтовая манипуляция 2 (через CodesInChaos)
- Текст: 12967,69 (в 38,4 раза быстрее)
- Приговор: 0,73 (37,9X быстрее)
- Байтовая манипуляция (через Валида Эйссу)
- Текст: 16 856,64 (в 29,5 раз быстрее)
- Предложение: 0,70 (в 39,5 раза быстрее)
- Lookup / Shift (через Натана Моинвазири)
- Текст: 23,201.23 (в 21,4 раза быстрее)
- Приговор: 1,24 (в 22,3 раза быстрее)
- Поиск по клеву (через Брайана Ламберта)
- Текст: 23 879,41 (в 20,8 раза быстрее)
- Приговор: 1,15 (в 23,9 раза быстрее)
BitConverter
(через Томалак)- Текст: 113 269,34 (в 4,4 раза быстрее)
- Приговор: 9,98 (в 2,8 раза быстрее)
{SoapHexBinary}.ToString
(через Майкрофт)- Текст: 178 601,39 (в 2,8 раза быстрее)
- Приговор: 10,68 (в 2,6 раза быстрее)
{byte}.ToString("X2")
(с помощьюforeach
) (получено из ответа Уилла Дина)- Текст: 308 805,38 (в 2,4 раза быстрее)
- Приговор: 16,89 (в 2,4 раза быстрее)
{byte}.ToString("X2")
(с помощью{IEnumerable}.Aggregate
, требуется System.Linq) (через Марк)- Текст: 352 828,20 (в 2,1 раза быстрее)
- Приговор: 16,87 (в 2,4 раза быстрее)
Array.ConvertAll
(с помощьюstring.Join
) (через Уилла Дина)- Текст: 675 451,57 (в 1,1 раза быстрее)
- Приговор: 17,95 (в 2,2 раза быстрее)
Array.ConvertAll
(с помощьюstring.Concat
Требуется.NET 4.0) (через Уилла Дина)- Текст: 752 078,70 (в 1,0 раза быстрее)
- Приговор: 18,28 (в 2,2 раза быстрее)
{StringBuilder}.AppendFormat
(с помощьюforeach
) (через Томалак)- Текст: 672 115,77 (в 1,1 раза быстрее)
- Приговор: 36,82 (в 1,1 раза быстрее)
{StringBuilder}.AppendFormat
(с помощью{IEnumerable}.Aggregate
, требует System.Linq) (получено из ответа Томалака)- Текст: 718 380,63 (в 1,0 раза быстрее)
- Приговор: 39,71 (в 1,0 раза быстрее)
Таблицы поиска взяли на себя инициативу по манипулированию байтами. По сути, существует некоторая форма предварительного вычисления того, каким будет любой заданный кусочек или байт в шестнадцатеричном формате. Затем, просматривая данные, вы просто просматриваете следующую часть, чтобы увидеть, какой это будет шестнадцатеричная строка. Это значение затем добавляется в результирующий вывод строки некоторым способом. В течение длительного времени манипулирование байтами, потенциально трудное для чтения некоторыми разработчиками, было наиболее эффективным подходом.
Ваша лучшая ставка по-прежнему будет найти некоторые репрезентативные данные и опробовать их в производственной среде. Если у вас разные ограничения памяти, вы можете предпочесть метод с меньшим количеством выделений, чем метод, который был бы быстрее, но потреблял бы больше памяти.
Код тестирования
Не стесняйтесь играть с кодом тестирования, который я использовал. Версия включена здесь, но не стесняйтесь клонировать репо и добавлять свои собственные методы. Пожалуйста, отправьте запрос на удаление, если вы найдете что-нибудь интересное или хотите помочь улучшить используемую им инфраструктуру тестирования.
- Добавьте новый статический метод (
Func<byte[], string>
) в /Tests/ConvertByteArrayToHexString/Test.cs. - Добавьте имя этого метода в
TestCandidates
возвращаемое значение в том же классе. - Убедитесь, что вы используете нужную вам версию ввода, предложение или текст, переключая комментарии в
GenerateTestInput
в том же классе. - Нажмите F5 и дождитесь вывода (в папке / bin также создается дамп HTML).
static string ByteArrayToHexStringViaStringJoinArrayConvertAll(byte[] bytes) {
return string.Join(string.Empty, Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaStringConcatArrayConvertAll(byte[] bytes) {
return string.Concat(Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaBitConverter(byte[] bytes) {
string hex = BitConverter.ToString(bytes);
return hex.Replace("-", "");
}
static string ByteArrayToHexStringViaStringBuilderAggregateByteToString(byte[] bytes) {
return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.Append(b.ToString("X2"))).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachByteToString(byte[] bytes) {
StringBuilder hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
hex.Append(b.ToString("X2"));
return hex.ToString();
}
static string ByteArrayToHexStringViaStringBuilderAggregateAppendFormat(byte[] bytes) {
return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.AppendFormat("{0:X2}", b)).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachAppendFormat(byte[] bytes) {
StringBuilder hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
hex.AppendFormat("{0:X2}", b);
return hex.ToString();
}
static string ByteArrayToHexViaByteManipulation(byte[] bytes) {
char[] c = new char[bytes.Length * 2];
byte b;
for (int i = 0; i < bytes.Length; i++) {
b = ((byte)(bytes[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(bytes[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
static string ByteArrayToHexViaByteManipulation2(byte[] bytes) {
char[] c = new char[bytes.Length * 2];
int b;
for (int i = 0; i < bytes.Length; i++) {
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
}
return new string(c);
}
static string ByteArrayToHexViaSoapHexBinary(byte[] bytes) {
SoapHexBinary soapHexBinary = new SoapHexBinary(bytes);
return soapHexBinary.ToString();
}
static string ByteArrayToHexViaLookupAndShift(byte[] bytes) {
StringBuilder result = new StringBuilder(bytes.Length * 2);
string hexAlphabet = "0123456789ABCDEF";
foreach (byte b in bytes) {
result.Append(hexAlphabet[(int)(b >> 4)]);
result.Append(hexAlphabet[(int)(b & 0xF)]);
}
return result.ToString();
}
static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_Lookup32, GCHandleType.Pinned).AddrOfPinnedObject();
static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes) {
var lookupP = _lookup32UnsafeP;
var result = new string((char)0, bytes.Length * 2);
fixed (byte* bytesP = bytes)
fixed (char* resultP = result) {
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++) {
resultP2[i] = lookupP[bytesP[i]];
}
}
return result;
}
static uint[] _Lookup32 = Enumerable.Range(0, 255).Select(i => {
string s = i.ToString("X2");
return ((uint)s[0]) + ((uint)s[1] << 16);
}).ToArray();
static string ByteArrayToHexViaLookupPerByte(byte[] bytes) {
var result = new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
var val = _Lookup32[bytes[i]];
result[2*i] = (char)val;
result[2*i + 1] = (char) (val >> 16);
}
return new string(result);
}
static string ByteArrayToHexViaLookup(byte[] bytes) {
string[] hexStringTable = new string[] {
"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F",
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F",
"20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F",
"30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F",
"40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F",
"50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F",
"60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F",
"70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F",
"80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F",
"90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F",
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF",
"B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF",
"C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF",
"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF",
"E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF",
"F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF",
};
StringBuilder result = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes) {
result.Append(hexStringTable[b]);
}
return result.ToString();
}
Обновление (2010-01-13)
Добавлен ответ Валида на анализ. Довольно быстро.
Обновление (2011-10-05)
добавленной string.Concat
Array.ConvertAll
вариант для полноты (требуется.NET 4.0). Наравне с string.Join
версия.
Обновление (2012-02-05)
Тестовое репо включает в себя больше вариантов, таких как StringBuilder.Append(b.ToString("X2"))
, Никто не расстроил результаты какие-либо. foreach
быстрее чем {IEnumerable}.Aggregate
например, но BitConverter
все еще выигрывает.
Обновление (2012-04-03)
Добавлены Майкрофта SoapHexBinary
ответ на анализ, который занял третье место.
Обновление (2013-01-15)
Добавлен ответ CodesInChaos по манипулированию байтами, который занял первое место (с большим запасом для больших блоков текста).
Обновление (2013-05-23)
Добавлен ответ поиска Натана Моинвазири и вариант из блога Брайана Ламберта. Оба довольно быстрые, но не идут впереди на тестовой машине, которую я использовал (AMD Phenom 9750).
Обновление (2014-07-31)
Добавлен новый байтовый ответ @CodesInChaos. Похоже, он взял на себя инициативу как в тестах предложений, так и в тестах полнотекстовых.
Обновление (2015-08-20)
Добавлены оптимизации airbreather и unsafe
вариант репо этого ответа. Если вы хотите играть в небезопасную игру, вы можете получить огромный выигрыш в производительности по сравнению с любым из предыдущих лучших победителей как в коротких строках, так и в больших текстах.
Есть класс с именем SoapHexBinary, который делает именно то, что вы хотите.
using System.Runtime.Remoting.Metadata.W3cXsd2001;
public static byte[] GetStringToBytes(string value)
{
SoapHexBinary shb = SoapHexBinary.Parse(value);
return shb.Value;
}
public static string GetBytesToString(byte[] value)
{
SoapHexBinary shb = new SoapHexBinary(value);
return shb.ToString();
}
При написании крипто-кода принято избегать зависимых от данных ветвей и поиска таблиц, чтобы гарантировать, что время выполнения не зависит от данных, поскольку зависящее от данных время может привести к атакам по побочным каналам.
Это также довольно быстро.
static string ByteToHexBitFiddle(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
int b;
for (int i = 0; i < bytes.Length; i++) {
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b-10)>>31)&-7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string(c);
}
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn
Оставь всякую надежду, вы, кто входит сюда
Объяснение странной мелочи:
bytes[i] >> 4
извлекает большой кусок байтаbytes[i] & 0xF
извлекает низкий кусочек байтаb - 10
является< 0
для ценностейb < 10
, который станет десятичной цифрой
является>= 0
для ценностейb > 10
, который станет письмом отA
вF
,- С помощью
i >> 31
32-разрядное целое число со знаком выделяет знак благодаря расширению знака. Это будет-1
заi < 0
а также0
заi >= 0
, - Сочетание 2) и 3) показывает, что
(b-10)>>31
будет0
для писем и-1
для цифр. - Глядя на регистр букв, последнее слагаемое становится
0
, а такжеb
находится в диапазоне от 10 до 15. Мы хотим сопоставить его сA
(65) доF
(70), что подразумевает добавление 55 ('A'-10
). - Глядя на регистр цифр, мы хотим адаптировать последнее слагаемое, чтобы оно отображало
b
от 0 до 9 до диапазона0
(48) до9
(57). Это означает, что он должен стать -7 ('0' - 55
).
Теперь мы можем просто умножить на 7. Но так как -1 представлен всеми битами, равными 1, мы можем вместо этого использовать& -7
поскольку(0 & -7) == 0
а также(-1 & -7) == -7
,
Некоторые дальнейшие соображения:
- Я не использовал вторую переменную цикла для индексации
c
, поскольку измерение показывает, что вычисление его изi
дешевле. - Используя именно
i < bytes.Length
поскольку верхняя граница цикла позволяет JITter исключить проверки границ наbytes[i]
Я выбрал этот вариант. - Изготовление
b
int допускает ненужные преобразования из и в байты.
Если вы хотите больше гибкости, чем BitConverter
, но не хотите этих неуклюжих явных циклов в стиле 1990-х, тогда вы можете сделать:
String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));
Или, если вы используете.NET 4.0:
String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));
(Последнее из комментария к оригинальному сообщению.)
Еще один подход на основе таблицы поиска. Этот использует только одну таблицу поиска для каждого байта, а не таблицу поиска для каждого куска.
private static readonly uint[] _lookup32 = CreateLookup32();
private static uint[] CreateLookup32()
{
var result = new uint[256];
for (int i = 0; i < 256; i++)
{
string s=i.ToString("X2");
result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
}
return result;
}
private static string ByteArrayToHexViaLookup32(byte[] bytes)
{
var lookup32 = _lookup32;
var result = new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
var val = lookup32[bytes[i]];
result[2*i] = (char)val;
result[2*i + 1] = (char) (val >> 16);
}
return new string(result);
}
Я также проверил варианты этого с помощью ushort
, struct{char X1, X2}
, struct{byte X1, X2}
в таблице поиска.
В зависимости от цели компиляции (x86, X64) они либо имели примерно одинаковую производительность, либо были немного медленнее, чем этот вариант.
И для еще более высокой производительности, его unsafe
родственный:
private static readonly uint[] _lookup32Unsafe = CreateLookup32Unsafe();
private static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();
private static uint[] CreateLookup32Unsafe()
{
var result = new uint[256];
for (int i = 0; i < 256; i++)
{
string s=i.ToString("X2");
if(BitConverter.IsLittleEndian)
result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
else
result[i] = ((uint)s[1]) + ((uint)s[0] << 16);
}
return result;
}
public static string ByteArrayToHexViaLookup32Unsafe(byte[] bytes)
{
var lookupP = _lookup32UnsafeP;
var result = new char[bytes.Length * 2];
fixed(byte* bytesP = bytes)
fixed (char* resultP = result)
{
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++)
{
resultP2[i] = lookupP[bytesP[i]];
}
}
return new string(result);
}
Или, если вы считаете приемлемым писать в строку напрямую:
public static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes)
{
var lookupP = _lookup32UnsafeP;
var result = new string((char)0, bytes.Length * 2);
fixed (byte* bytesP = bytes)
fixed (char* resultP = result)
{
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++)
{
resultP2[i] = lookupP[bytesP[i]];
}
}
return result;
}
Вы можете использовать метод BitConverter.ToString:
byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256}
Console.WriteLine( BitConverter.ToString(bytes));
Выход:
00-01-02-04-08-10-20-40-80-FF
Дополнительная информация: метод BitConverter.ToString (Byte[])
Я только что столкнулся с той же проблемой сегодня, и я наткнулся на этот код:
private static string ByteArrayToHex(byte[] barray)
{
char[] c = new char[barray.Length * 2];
byte b;
for (int i = 0; i < barray.Length; ++i)
{
b = ((byte)(barray[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
Источник: сообщение на форуме byte [] Array to Hex String (см. Сообщение PZahra). Я немного изменил код, чтобы удалить префикс 0x.
Я провел некоторое тестирование производительности в коде, и это было почти в восемь раз быстрее, чем при использовании BitConverter.ToString() (самый быстрый согласно сообщению Патриджа).
Начиная с.NET 5 RC2 вы можете использовать:
-
Convert.ToHexString(byte[] inArray)
который возвращаетstring
и -
Convert.FromHexString(string s)
который возвращаетbyte[]
.
Доступны перегрузки, которые принимают параметры диапазона.
Преобразование byte[] в шестнадцатеричную строку — анализ производительности
Обновлено: 2022-04-17
Начиная с .NET 5 вы должны использовать Convert.ToHexString(bytes[])!
using System;
string result = Convert.ToHexString(bytesToConvert);
Об этой таблице лидеров и тесте
Сравнение с user356218 кажется устаревшим и неполным, особенно после .NET 5 с его .NET 5, поэтому я решил ~~упасть в байты на кроличью нору шестнадцатеричной строки~~ создать новое, обновленное сравнение с большим количеством методов из ответов на оба вопроса. эти два вопроса.
Я использовал BenchamrkDotNet вместо специального скрипта для сравнительного анализа, который, надеюсь, сделает результат более точным.
Всегда помните, что любой микро-бенчмаркинг никогда не будет отражать реальную ситуацию, и вы должны проводить свои собственные тесты.
Я провел эти тесты в Linux с ядром 5.15.32 на AMD Ryzen 5800H с 2x8 ГБ DDR4 @ 2133 МГц.
Имейте в виду, что выполнение всего теста может занять много времени — около 40 минут на моей машине.
ВЕРХНИЙ РЕГИСТР (с заглавной буквы) против вывода в нижнем регистре
Все упомянутые методы (если не указано иное) фокусируются только на выводе ЗАГЛАВНЫХ РЕГИСТРОВ . Это означает, что вывод будет выглядеть так
B33F69
, нет
b33f69
.
Выход из
Convert.ToHexString
всегда в верхнем регистре, но, к счастью, в сочетании с
ToLower()
, хотя оба метода будут быстрее, если вас это беспокоит.
Эффективное преобразование строки в нижний регистр может быть проблемой в некоторых методах (особенно в тех, которые используют магию битовых операторов), но в большинстве случаев достаточно просто изменить параметр.
X2
к
x2
или измените буквы с прописных на строчные в отображении.
Таблица лидеров
Отсортировано по
Mean N=100
. Ориентиром является метод StringBuilderForEachByte .
Вывод
Метод
ConvertToHexString
без сомнения, самый быстрый из существующих, и, на мой взгляд, его следует использовать всегда, если у вас есть возможность — он очень быстрый и очень чистый.
using System;
string result = Convert.ToHexString(bytesToConvert);
Если нет, я решил выделить два других метода, которые я считаю достойными ниже. Я решил не выделять методы, так как такой код может быть не только, ну, небезопасным , но и большинство проектов, с которыми я работал, вообще не допускают такой код.
Достойные упоминания
Первый из них
LookupPerByteSpan
.
Код почти идентичен коду в
LookupPerByte
от user445517 из этого ответа . Это самый быстрый не-
unsafe
метод бенчмаркинга. Разница между оригиналом и этим заключается в использовании выделения стека для более коротких входных данных (до 512 байт). Это делает этот метод примерно на 10 % быстрее для этих входных данных, но примерно на 5 % медленнее для больших. Поскольку большинство данных, с которыми я работаю, короче, чем больше, я выбрал этот.
LookupSpanPerByteSpan
также очень быстр, но размер кода его отображения слишком велик по сравнению со всеми другими методами.
private static readonly uint[] Lookup32 = Enumerable.Range(0, 256).Select(i =>
{
string s = i.ToString("X2");
return s[0] + ((uint)s[1] << 16);
}).ToArray();
public string ToHexString(byte[] bytes)
{
var result = bytes.Length * 2 <= 1024
? stackalloc char[bytes.Length * 2]
: new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
var val = Lookup32[bytes[i]];
result[2 * i] = (char)val;
result[2 * i + 1] = (char)(val >> 16);
}
return new string(result);
}
Второй
LookupAndShiftAlphabetSpanMultiply
. Во-первых, я хотел бы упомянуть, что это мое собственное творение. Я считаю, что этот метод не только довольно быстрый, но и простой для понимания. Скорость обусловлена изменением, произошедшим в C# 7.3, где объявлено
ReadOnlySpan<byte>
методы, возвращающие инициализацию
константного массива -new byte {1, 2, 3, ...}
- компилируются как статические данные программы, поэтому избыточный memcpy не используется. [источник]
private static ReadOnlySpan<byte> HexAlphabetSpan => new[]
{
(byte)'0', (byte)'1', (byte)'2', (byte)'3',
(byte)'4', (byte)'5', (byte)'6', (byte)'7',
(byte)'8', (byte)'9', (byte)'A', (byte)'B',
(byte)'C', (byte)'D', (byte)'E', (byte)'F'
};
public static string ToHexString(byte[] bytes)
{
var res = bytes.Length * 2 <= 1024 ? stackalloc char[bytes.Length * 2] : new char[bytes.Length * 2];
for (var i = 0; i < bytes.Length; ++i)
{
var j = i * 2;
res[j] = (char)HexAlphabetSpan[bytes[i] >> 4];
res[j + 1] = (char)HexAlphabetSpan[bytes[i] & 0xF];
}
return new string(res);
}
Исходный код
Исходный код всех методов, бенчмарк и этот ответ можно найти здесь в виде Gist на моем GitHub.
Это ответ на 4-ю редакцию очень популярного ответа Томалака (и последующие правки).
Я сделаю так, что это редактирование неверно, и объясню, почему оно может быть отменено. Попутно вы можете узнать кое-что о некоторых внутренних элементах и увидеть еще один пример того, что такое преждевременная оптимизация и как она может вас укусить.
tl; dr: просто используйте Convert.ToByte
а также String.Substring
если вы спешите ("Оригинальный код" ниже), то это лучшая комбинация, если вы не хотите повторно реализовывать Convert.ToByte
, Используйте что-то более продвинутое (см. Другие ответы), которое не использует Convert.ToByte
если вам нужна производительность. Не используйте ничего, кроме String.Substring
в комбинации с Convert.ToByte
Если кто-то не может сказать что-то интересное в комментариях к этому ответу.
предупреждение: этот ответ может устареть, если Convert.ToByte(char[], Int32)
перегрузка реализована в фреймворке. Это вряд ли произойдет в ближайшее время.
Как правило, я не очень люблю говорить "не оптимизировать преждевременно", потому что никто не знает, когда "преждевременно". Единственное, что вы должны учитывать при принятии решения об оптимизации или нет, это: "Есть ли у меня время и ресурсы для правильного исследования подходов к оптимизации?". Если вы этого не сделаете, тогда слишком рано, подождите, пока ваш проект не станет более зрелым или пока вам не понадобится производительность (если есть реальная необходимость, тогда вы найдете время). А пока сделайте самое простое, что могло бы сработать.
Оригинальный код:
public static byte[] HexadecimalStringToByteArray_Original(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
for (var i = 0; i < outputLength; i++)
output[i] = Convert.ToByte(input.Substring(i * 2, 2), 16);
return output;
}
Редакция 4:
public static byte[] HexadecimalStringToByteArray_Rev4(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
output[i] = Convert.ToByte(new string(new char[2] { (char)sr.Read(), (char)sr.Read() }), 16);
}
return output;
}
Ревизия избегает String.Substring
и использует StringReader
вместо. Данная причина:
Изменить: вы можете улучшить производительность для длинных строк с помощью однопроходного парсера, например так:
Ну, глядя на код ссылки для String.Substring
уже явно "однопроходный"; и почему не должно быть? Он работает на уровне байтов, а не на суррогатных парах.
Тем не менее, он выделяет новую строку, но тогда вам нужно выделить одну для передачи Convert.ToByte
тем не мение. Кроме того, решение, представленное в ревизии, выделяет еще один объект на каждой итерации (массив из двух символов); Вы можете безопасно поместить это распределение за пределы цикла и повторно использовать массив, чтобы избежать этого.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
{
numeral[0] = (char)sr.Read();
numeral[1] = (char)sr.Read();
output[i] = Convert.ToByte(new string(numeral), 16);
}
}
return output;
}
Каждый шестнадцатеричный numeral
представляет один октет с использованием двух цифр (символов).
Но тогда зачем звонить StringReader.Read
дважды? Просто вызовите его вторую перегрузку и попросите его прочитать сразу два символа в массиве из двух символов; и уменьшить количество звонков на два.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
{
var read = sr.Read(numeral, 0, 2);
Debug.Assert(read == 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
}
return output;
}
То, что у вас осталось, - это программа чтения строк, единственным добавленным "значением" которой является параллельный индекс (внутренний _pos
) который вы могли бы заявить о себе (как j
например), избыточная переменная длины (внутренняя _length
) и избыточная ссылка на входную строку (внутренняя _s
). Другими словами, это бесполезно.
Если вам интересно, как Read
"читает", просто посмотрите на код, все, что он делает, это вызывает String.CopyTo
на входной строке. Все остальное - просто накладные расходы на поддержание ценностей, которые нам не нужны.
Итак, удалите читатель строки и вызовите CopyTo
сам; это проще, понятнее и эффективнее.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
for (int i = 0, j = 0; i < outputLength; i++, j += 2)
{
input.CopyTo(j, numeral, 0, 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
return output;
}
Вы действительно нуждаетесь в j
Индекс, который увеличивается с шагом в две параллели к i
? Конечно нет, просто умножьте i
на два (которые компилятор должен уметь оптимизировать до сложения).
public static byte[] HexadecimalStringToByteArray_BestEffort(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
for (int i = 0; i < outputLength; i++)
{
input.CopyTo(i * 2, numeral, 0, 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
return output;
}
Как выглядит решение сейчас? Точно так же, как это было в начале, только вместо того, чтобы использовать String.Substring
чтобы выделить строку и скопировать в нее данные, вы используете промежуточный массив, в который вы копируете шестнадцатеричные цифры, затем самостоятельно выделяете строку и снова копируете данные из массива в строку (когда вы передаете ее в строковый конструктор). Вторая копия может быть оптимизирована, если строка уже находится в пуле интернов, но затем String.Substring
также сможет избежать этого в этих случаях.
На самом деле, если вы посмотрите на String.Substring
опять же, вы видите, что он использует некоторые низкоуровневые внутренние знания о том, как строятся строки, чтобы распределить строку быстрее, чем вы обычно это делаете, и он вставляет тот же код, который используется CopyTo
прямо там, чтобы избежать накладных расходов.
String.Substring
- В худшем случае: одно быстрое размещение, одна быстрая копия.
- В лучшем случае: нет выделения, нет копии.
Ручной метод
- В худшем случае: два обычных размещения, одно обычное копирование, одно быстрое копирование.
- В лучшем случае: одно нормальное распределение, одна нормальная копия.
Заключение? Если вы хотите использовать Convert.ToByte(String, Int32)
(потому что вы не хотите самостоятельно реализовывать эту функциональность), похоже, нет способа победить String.Substring
; все, что вы делаете, это бегаете кругами, заново изобретая колесо (только с неоптимальными материалами).
Обратите внимание, что с помощью Convert.ToByte
а также String.Substring
это совершенно правильный выбор, если вам не нужны экстремальные характеристики. Помните: выбирайте альтернативу, только если у вас есть время и ресурсы, чтобы выяснить, как она работает правильно.
Если бы был Convert.ToByte(char[], Int32)
конечно, все было бы иначе (можно было бы сделать то, что я описал выше, и полностью избежать String
).
Я подозреваю, что люди, которые сообщают о лучшей производительности, "избегая String.Substring
"Также избегайте Convert.ToByte(String, Int32)
, что вы действительно должны делать, если вам все равно нужна производительность. Посмотрите на бесчисленное множество других ответов, чтобы обнаружить все различные подходы к этому.
Отказ от ответственности: я не декомпилировал последнюю версию фреймворка, чтобы убедиться, что справочный источник обновлен, я предполагаю, что это так.
Теперь все это звучит хорошо и логично, надеюсь, даже очевидно, если вам удалось продвинуться так далеко. Но так ли это?
Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz
Cores: 8
Current Clock Speed: 2600
Max Clock Speed: 2600
--------------------
Parsing hexadecimal string into an array of bytes
--------------------
HexadecimalStringToByteArray_Original: 7,777.09 average ticks (over 10000 runs), 1.2X
HexadecimalStringToByteArray_BestEffort: 8,550.82 average ticks (over 10000 runs), 1.1X
HexadecimalStringToByteArray_Rev4: 9,218.03 average ticks (over 10000 runs), 1.0X
Да!
Реквизит в Partridge для каркаса скамейки, его легко взломать. В качестве входных данных используется следующий хэш SHA-1, повторенный 5000 раз, чтобы получить строку длиной 100 000 байт.
209113288F93A9AB8E474EA78D899AFDBB874355
Повеселись! (Но оптимизировать с модерацией.)
Dotnet 5 Обновить
Чтобы преобразовать из (байтового массива) в шестнадцатеричный, используйте:
System.Convert.ToHexString
var myBytes = new byte[100];
var myString = System.Convert.ToHexString(myBytes);
Чтобы преобразовать из шестнадцатеричного
string
к
byte[]
, использовать:
System.Convert.FromHexString
var myString = "E10B116E8530A340BCC7B3EAC208487B";
var myBytes = System.Convert.FromHexString(myString);
Дополнение к ответу @CodesInChaos (обратный метод)
public static byte[] HexToByteUsingByteManipulation(string s)
{
byte[] bytes = new byte[s.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
int hi = s[i*2] - 65;
hi = hi + 10 + ((hi >> 31) & 7);
int lo = s[i*2 + 1] - 65;
lo = lo + 10 + ((lo >> 31) & 7) & 0x0f;
bytes[i] = (byte) (lo | hi << 4);
}
return bytes;
}
Объяснение:
& 0x0f
должен поддерживать также строчные буквы
hi = hi + 10 + ((hi >> 31) & 7);
такой же как:
hi = ch-65 + 10 + (((ch-65) >> 31) & 7);
Для "0".. "9" это так же, как hi = ch - 65 + 10 + 7;
который hi = ch - 48
(это из-за 0xffffffff & 7
).
Для "А".. "F" это hi = ch - 65 + 10;
(это из-за 0x00000000 & 7
).
Для 'a'.. 'f' мы должны получить большие числа, поэтому мы должны вычесть 32 из версии по умолчанию, сделав несколько битов 0
используя & 0x0f
,
65 код для 'A'
48 код для '0'
7 - количество букв между '9'
а также 'A'
в таблице ASCII (...456789:;<=>?@ABCD...
).
Эту проблему также можно решить с помощью справочной таблицы. Это потребует небольшого количества статической памяти как для кодера, так и для декодера. Однако этот метод будет быстрым:
- Таблица кодировщика 512 байт или 1024 байт (в два раза больше, если требуется прописная и строчная буквы)
- Таблица декодера 256 байтов или 64 КиБ (либо поиск одного символа, либо поиск двух символов)
Мое решение использует 1024 байта для таблицы кодирования и 256 байтов для декодирования.
расшифровка
private static readonly byte[] LookupTable = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static byte Lookup(char c)
{
var b = LookupTable[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
public static byte ToByte(char[] chars, int offset)
{
return (byte)(Lookup(chars[offset]) << 4 | Lookup(chars[offset + 1]));
}
кодирование
private static readonly char[][] LookupTableUpper;
private static readonly char[][] LookupTableLower;
static Hex()
{
LookupTableLower = new char[256][];
LookupTableUpper = new char[256][];
for (var i = 0; i < 256; i++)
{
LookupTableLower[i] = i.ToString("x2").ToCharArray();
LookupTableUpper[i] = i.ToString("X2").ToCharArray();
}
}
public static char[] ToCharLower(byte[] b, int bOffset)
{
return LookupTableLower[b[bOffset]];
}
public static char[] ToCharUpper(byte[] b, int bOffset)
{
return LookupTableUpper[b[bOffset]];
}
сравнение
StringBuilderToStringFromBytes: 106148
BitConverterToStringFromBytes: 15783
ArrayConvertAllToStringFromBytes: 54290
ByteManipulationToCharArray: 8444
TableBasedToCharArray: 5651 *
* это решение
Заметка
Во время декодирования могут возникнуть IOException и IndexOutOfRangeException (если символ имеет слишком высокое значение> 256). Должны быть реализованы методы для де / кодирования потоков или массивов, это просто подтверждение концепции.
Зачем делать это сложным? Это просто в Visual Studio 2008:
C#:
string hex = BitConverter.ToString(YourByteArray).Replace("-", "");
VB:
Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Это отличная статья. Мне нравится решение Валида. Я не прошел тест Патриджа, но, похоже, он проходит довольно быстро. Мне также понадобился обратный процесс, преобразование шестнадцатеричной строки в байтовый массив, поэтому я написал ее как обращение решения Валида. Не уверен, что это быстрее, чем оригинальное решение Томалака. Опять же, я не запускал обратный процесс через тест Патриджа.
private byte[] HexStringToByteArray(string hexString)
{
int hexStringLength = hexString.Length;
byte[] b = new byte[hexStringLength / 2];
for (int i = 0; i < hexStringLength; i += 2)
{
int topChar = (hexString[i] > 0x40 ? hexString[i] - 0x37 : hexString[i] - 0x30) << 4;
int bottomChar = hexString[i + 1] > 0x40 ? hexString[i + 1] - 0x37 : hexString[i + 1] - 0x30;
b[i / 2] = Convert.ToByte(topChar + bottomChar);
}
return b;
}
Чтобы не отвечать на многие ответы здесь, я нашел довольно оптимальную (~4,5 раза лучше, чем принято) прямую реализацию синтаксического анализатора шестнадцатеричных строк. Во-первых, вывод моих тестов (первая партия - моя реализация):
Give me that string:
04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939f
Time to parse 100,000 times: 50.4192 ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Accepted answer: (StringToByteArray)
Time to parse 100000 times: 233.1264ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With Mono's implementation:
Time to parse 100000 times: 777.2544ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With SoapHexBinary:
Time to parse 100000 times: 845.1456ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Строки base64 и BitConverter'd должны проверить правильность. Обратите внимание, что они равны.
Реализация:
public static byte[] ToByteArrayFromHex(string hexString)
{
if (hexString.Length % 2 != 0) throw new ArgumentException("String must have an even length");
var array = new byte[hexString.Length / 2];
for (int i = 0; i < hexString.Length; i += 2)
{
array[i/2] = ByteFromTwoChars(hexString[i], hexString[i + 1]);
}
return array;
}
private static byte ByteFromTwoChars(char p, char p_2)
{
byte ret;
if (p <= '9' && p >= '0')
{
ret = (byte) ((p - '0') << 4);
}
else if (p <= 'f' && p >= 'a')
{
ret = (byte) ((p - 'a' + 10) << 4);
}
else if (p <= 'F' && p >= 'A')
{
ret = (byte) ((p - 'A' + 10) << 4);
} else throw new ArgumentException("Char is not a hex digit: " + p,"p");
if (p_2 <= '9' && p_2 >= '0')
{
ret |= (byte) ((p_2 - '0'));
}
else if (p_2 <= 'f' && p_2 >= 'a')
{
ret |= (byte) ((p_2 - 'a' + 10));
}
else if (p_2 <= 'F' && p_2 >= 'A')
{
ret |= (byte) ((p_2 - 'A' + 10));
} else throw new ArgumentException("Char is not a hex digit: " + p_2, "p_2");
return ret;
}
Я попробовал кое-что с unsafe
и перемещение (явно избыточного) персонажа в клев if
Последовательность к другому методу, но это было быстрее всего.
(Я признаю, что это отвечает на половину вопроса. Я чувствовал, что преобразование string->byte[] было недостаточно представлено, в то время как угол строки byte[]->, кажется, хорошо покрыт. Таким образом, этот ответ.)
От разработчиков Microsoft, хорошее, простое преобразование:
public static string ByteArrayToString(byte[] ba)
{
// Concatenate the bytes into one long string
return ba.Aggregate(new StringBuilder(32),
(sb, b) => sb.Append(b.ToString("X2"))
).ToString();
}
Несмотря на то, что вышесказанное является чистым компактом, любители производительности будут кричать об этом, используя счетчики. Вы можете получить максимальную производительность с улучшенной версией оригинального ответа Tomolak:
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
for(int i=0; i < ga.Length; i++) // <-- Use for loop is faster than foreach
hex.Append(ba[i].ToString("X2")); // <-- ToString is faster than AppendFormat
return hex.ToString();
}
Это самая быстрая из всех подпрограмм, которые я видел здесь до сих пор. Не поверьте мне на слово... протестируйте производительность каждой подпрограммы и проверьте ее код CIL для себя.
Безопасные версии:
public static class HexHelper
{
[System.Diagnostics.Contracts.Pure]
public static string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string hexAlphabet = @"0123456789ABCDEF";
var chars = new char[checked(value.Length * 2)];
unchecked
{
for (int i = 0; i < value.Length; i++)
{
chars[i * 2] = hexAlphabet[value[i] >> 4];
chars[i * 2 + 1] = hexAlphabet[value[i] & 0xF];
}
}
return new string(chars);
}
[System.Diagnostics.Contracts.Pure]
public static byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = value[i * 2]; // High 4 bits.
int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
b = value[i * 2 + 1]; // Low 4 bits.
val += (b - '0') + ((('9' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
return result;
}
}
}
Небезопасные версии Для тех, кто предпочитает производительность и не боится небезопасности. На 35% быстрее ToHex и на 10% быстрее FromHex.
public static class HexUnsafeHelper
{
[System.Diagnostics.Contracts.Pure]
public static unsafe string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string alphabet = @"0123456789ABCDEF";
string result = new string(' ', checked(value.Length * 2));
fixed (char* alphabetPtr = alphabet)
fixed (char* resultPtr = result)
{
char* ptr = resultPtr;
unchecked
{
for (int i = 0; i < value.Length; i++)
{
*ptr++ = *(alphabetPtr + (value[i] >> 4));
*ptr++ = *(alphabetPtr + (value[i] & 0xF));
}
}
}
return result;
}
[System.Diagnostics.Contracts.Pure]
public static unsafe byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
fixed (char* valuePtr = value)
{
char* valPtr = valuePtr;
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = *valPtr++; // High 4 bits.
int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
b = *valPtr++; // Low 4 bits.
val += (b - '0') + ((('9' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
}
return result;
}
}
}
Кстати, для тестирования производительности инициализация алфавита каждый раз, когда вызываемая функция преобразования неверна, алфавит должен быть const (для строки) или статическим readonly (для char[]). Затем основанное на алфавите преобразование byte[] в строку становится таким же быстрым, как версии манипулирования байтами.
И, конечно, тест должен быть скомпилирован в Release (с оптимизацией) и с отключенной опцией "Отключить оптимизацию JIT" (то же самое для "Enable Just My Code", если код должен быть отлаживаемым).
Самый быстрый способ для людей старой школы... скучаю по указателям
static public byte[] HexStrToByteArray(string str)
{
byte[] res = new byte[(str.Length % 2 != 0 ? 0 : str.Length / 2)]; //check and allocate memory
for (int i = 0, j = 0; j < res.Length; i += 2, j++) //convert loop
res[j] = (byte)((str[i] % 32 + 9) % 25 * 16 + (str[i + 1] % 32 + 9) % 25);
return res;
}
.NET 5 добавил метод Convert.ToHexString .
Для тех, кто использует старую версию .NET
internal static class ByteArrayExtensions
{
public static string ToHexString(this byte[] bytes, Casing casing = Casing.Upper)
{
Span<char> result = stackalloc char[0];
if (bytes.Length > 16)
{
var array = new char[bytes.Length * 2];
result = array.AsSpan();
}
else
{
result = stackalloc char[bytes.Length * 2];
}
int pos = 0;
foreach (byte b in bytes)
{
ToCharsBuffer(b, result, pos, casing);
pos += 2;
}
return result.ToString();
}
private static void ToCharsBuffer(byte value, Span<char> buffer, int startingIndex = 0, Casing casing = Casing.Upper)
{
uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U;
uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing;
buffer[startingIndex + 1] = (char)(packedResult & 0xFF);
buffer[startingIndex] = (char)(packedResult >> 8);
}
}
public enum Casing : uint
{
// Output [ '0' .. '9' ] and [ 'A' .. 'F' ].
Upper = 0,
// Output [ '0' .. '9' ] and [ 'a' .. 'f' ].
Lower = 0x2020U,
}
Адаптировано из репозитория .NET https://github.com/dotnet/runtime/blob/v5.0.3/src/libraries/System.Private.CoreLib/src/System/Convert.cshttps://github.com/dotnet/runtime/blob/v5.0.3/src/libraries/Common/src/System/HexConverter.cs
Обратная функция для кода Валида Эйссы (Hex String To Byte Array):
public static byte[] HexToBytes(this string hexString)
{
byte[] b = new byte[hexString.Length / 2];
char c;
for (int i = 0; i < hexString.Length / 2; i++)
{
c = hexString[i * 2];
b[i] = (byte)((c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57)) << 4);
c = hexString[i * 2 + 1];
b[i] += (byte)(c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57));
}
return b;
}
Функция Waleed Eissa с поддержкой нижнего регистра:
public static string BytesToHex(this byte[] barray, bool toLowerCase = true)
{
byte addByte = 0x37;
if (toLowerCase) addByte = 0x57;
char[] c = new char[barray.Length * 2];
byte b;
for (int i = 0; i < barray.Length; ++i)
{
b = ((byte)(barray[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + addByte : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + addByte : b + 0x30);
}
return new string(c);
}
Методы расширения (отказ от ответственности: полностью непроверенный код, кстати...):
public static class ByteExtensions
{
public static string ToHexString(this byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
{
hex.AppendFormat("{0:x2}", b);
}
return hex.ToString();
}
}
и т. д. Используйте одно из трех решений Томалака (последнее из которых является методом расширения строки).
Тесты: шестнадцатеричная строка в байтовый массив
Я заметил, что большинство тестов было предопределено для функций, которые преобразуют массив байтов в строку Hex. Итак, в этом посте я сосредоточусь на другой стороне: функциях, преобразующих шестнадцатеричную строку в байтовый массив. если вас интересует только результат, вы можете перейти к разделу « Сводка ». файл тестового кода предоставляется в конце сообщения.
Этикетки
Я хотел бы назвать функцию из принятого ответа (Томалака) StringToByteArrayV1 или сократить ее до V1. остальные функции будут называться так же: V2, V3, V4, ... и т. д.
Указатель участвующих функций
- StringToByteArrayV1 от Tomalak (принятый ответ)
- StringToByteArrayV2 от Mykroft (с использованием SoapHexBinary)
- ( StringToByteArrayV3 от drphrozenсправочная таблица)
- (манипулирование )
- (Манипулирование байтами)
- (V5_1 + улучшил его на основе комментария Амира Резаи)
- (V5_2 + улучшил его на основе комментария Бена Фойгта) (вы можете увидеть его окончательную форму в этом посте, опубликованном тестовом коде)
- StringToByteArrayV6, автор: Бен Мошер (манипулирование байтами)
- (Byte Manipulation - безопасная версия)
- StringToByteArrayV7 от MaratiusStringToByteArrayV8 от Maratius (Byte Manipulation - небезопасная версия)
- StringToByteArrayV10, автор: AlejandroAlis
- StringToByteArrayV11, автор - Фредрик Ху
- StringToByteArrayV12, автор: Маартен Бодевес
- StringToByteArrayV13, автор - ClausAndersen
- StringToByteArrayV14 - Стас Макутин
- StringToByteArrayV15, автор: JJJ
- StringToByteArrayV16, автор: JamieSee
- StringToByteArrayV18, автор Грегори Морс
- StringToByteArrayV19, автор: Рик
- StringToByteArrayV20 пользователя SandRock
- StringToByteArrayV21, автор: Пол
Тест на правильность
Я проверил правильность, передав все 256 возможных значений 1 байта, а затем проверив вывод, чтобы убедиться, что он правильный. Результат:
- У V18 есть проблема со строками, начинающимися с «00» (см. Комментарий Роджера Стюарта). кроме этого он проходит все тесты.
- если шестнадцатеричные строки алфавита в верхнем регистре: все функции успешно пройдены
- если шестнадцатеричные буквы латинского алфавита являются строчными, то следующие функции не работают: V5_1, V5_2, v7, V8, V15, V19
примечание: V5_3 решает эту проблему (V5_1 и V5_2)
Perofmance Test
Я сделал предварительные тесты с использованием класса Stopwatch.
- Исполнение для длинных струн
input length: 10,000,000 bytes
runs: 100
average elapsed time per run:
V1 = 136.4ms
V2 = 104.5ms
V3 = 22.0ms
V4 = 9.9ms
V5_1 = 10.2ms
V5_2 = 9.0ms
V5_3 = 9.3ms
V6 = 18.3ms
V7 = 9.8ms
V8 = 8.8ms
V9 = 10.2ms
V10 = 19.0ms
V11 = 12.2ms
V12 = 27.4ms
V13 = 21.8ms
V14 = 12.0ms
V15 = 14.9ms
V16 = 15.3ms
V17 = 9.5ms
V18 got excluded from this test, because it was very slow when using very long string
V19 = 222.8ms
V20 = 66.0ms
V21 = 15.4ms
V1 average ticks per run: 1363529.4
V2 is more fast than V1 by: 1.3 times (ticks ratio)
V3 is more fast than V1 by: 6.2 times (ticks ratio)
V4 is more fast than V1 by: 13.8 times (ticks ratio)
V5_1 is more fast than V1 by: 13.3 times (ticks ratio)
V5_2 is more fast than V1 by: 15.2 times (ticks ratio)
V5_3 is more fast than V1 by: 14.8 times (ticks ratio)
V6 is more fast than V1 by: 7.4 times (ticks ratio)
V7 is more fast than V1 by: 13.9 times (ticks ratio)
V8 is more fast than V1 by: 15.4 times (ticks ratio)
V9 is more fast than V1 by: 13.4 times (ticks ratio)
V10 is more fast than V1 by: 7.2 times (ticks ratio)
V11 is more fast than V1 by: 11.1 times (ticks ratio)
V12 is more fast than V1 by: 5.0 times (ticks ratio)
V13 is more fast than V1 by: 6.3 times (ticks ratio)
V14 is more fast than V1 by: 11.4 times (ticks ratio)
V15 is more fast than V1 by: 9.2 times (ticks ratio)
V16 is more fast than V1 by: 8.9 times (ticks ratio)
V17 is more fast than V1 by: 14.4 times (ticks ratio)
V19 is more SLOW than V1 by: 1.6 times (ticks ratio)
V20 is more fast than V1 by: 2.1 times (ticks ratio)
V21 is more fast than V1 by: 8.9 times (ticks ratio)
- Исполнение V18 для длинных струн
V18 took long time at the previous test,
so let's decrease length for it:
input length: 1,000,000 bytes
runs: 100
average elapsed time per run: V1 = 14.1ms , V18 = 146.7ms
V1 average ticks per run: 140630.3
V18 is more SLOW than V1 by: 10.4 times (ticks ratio)
- Исполнение для коротких струн
input length: 100 byte
runs: 1,000,000
V1 average ticks per run: 14.6
V2 is more fast than V1 by: 1.4 times (ticks ratio)
V3 is more fast than V1 by: 5.9 times (ticks ratio)
V4 is more fast than V1 by: 15.7 times (ticks ratio)
V5_1 is more fast than V1 by: 15.1 times (ticks ratio)
V5_2 is more fast than V1 by: 18.4 times (ticks ratio)
V5_3 is more fast than V1 by: 16.3 times (ticks ratio)
V6 is more fast than V1 by: 5.3 times (ticks ratio)
V7 is more fast than V1 by: 15.7 times (ticks ratio)
V8 is more fast than V1 by: 18.0 times (ticks ratio)
V9 is more fast than V1 by: 15.5 times (ticks ratio)
V10 is more fast than V1 by: 7.8 times (ticks ratio)
V11 is more fast than V1 by: 12.4 times (ticks ratio)
V12 is more fast than V1 by: 5.3 times (ticks ratio)
V13 is more fast than V1 by: 5.2 times (ticks ratio)
V14 is more fast than V1 by: 13.4 times (ticks ratio)
V15 is more fast than V1 by: 9.9 times (ticks ratio)
V16 is more fast than V1 by: 9.2 times (ticks ratio)
V17 is more fast than V1 by: 16.2 times (ticks ratio)
V18 is more fast than V1 by: 1.1 times (ticks ratio)
V19 is more SLOW than V1 by: 1.6 times (ticks ratio)
V20 is more fast than V1 by: 1.9 times (ticks ratio)
V21 is more fast than V1 by: 11.4 times (ticks ratio)
Код тестирования
Перед использованием любого кода из следующего кода прочитать раздел об отказе от ответственности здесь, в этом сообщении, рекомендуетсяhttps://github.com/Ghosticollis/performance-tests/blob/main/MTestPerformance.cs
Резюме
Я рекомендую использовать одну из следующих функций из-за хорошей производительности и поддержки как верхнего, так и нижнего регистра:
- StringToByteArrayV4 от CoperNickStringToByteArrayV4, автор: CoperNickбайтами
- StringToByteArrayV9 от GeographStringToByteArrayV9 от Geograph
- StringToByteArrayV17 пользователя spacepilleStringToByteArrayV17 пользователя spacepille
- StringToByteArrayV5_1, автор Chris FStringToByteArrayV5_3 от Chris FStringToByteArrayV5_3 от Chris FStringToByteArrayV5_2 от Chris F (V5_2 + улучшил его на основе комментария Бена Фойгта) (вы можете увидеть его окончательную форму в этом посте, опубликованном тестовом коде)
Отказ от ответственности
ВНИМАНИЕ: у меня нет должных знаний в области тестирования. Основная цель этих примитивных тестов - дать быстрый обзор того, что может быть хорошим из всех опубликованных функций. если вам нужны точные результаты, используйте соответствующие инструменты тестирования.
наконец, я хотел бы сказать, что я новичок в работе с stackoverflow, извините, если мой пост отсутствует. комментарии для улучшения этого сообщения были бы признательны.
Я приму участие в этом соревновании с битами, так как у меня есть ответ, который также использует биты для декодирования шестнадцатеричных чисел. Обратите внимание, что использование символьных массивов может быть даже быстрее, чем вызов StringBuilder
методы также потребуют времени.
public static String ToHex (byte[] data)
{
int dataLength = data.Length;
// pre-create the stringbuilder using the length of the data * 2, precisely enough
StringBuilder sb = new StringBuilder (dataLength * 2);
for (int i = 0; i < dataLength; i++) {
int b = data [i];
// check using calculation over bits to see if first tuple is a letter
// isLetter is zero if it is a digit, 1 if it is a letter
int isLetter = (b >> 7) & ((b >> 6) | (b >> 5)) & 1;
// calculate the code using a multiplication to make up the difference between
// a digit character and an alphanumerical character
int code = '0' + ((b >> 4) & 0xF) + isLetter * ('A' - '9' - 1);
// now append the result, after casting the code point to a character
sb.Append ((Char)code);
// do the same with the lower (less significant) tuple
isLetter = (b >> 3) & ((b >> 2) | (b >> 1)) & 1;
code = '0' + (b & 0xF) + isLetter * ('A' - '9' - 1);
sb.Append ((Char)code);
}
return sb.ToString ();
}
public static byte[] FromHex (String hex)
{
// pre-create the array
int resultLength = hex.Length / 2;
byte[] result = new byte[resultLength];
// set validity = 0 (0 = valid, anything else is not valid)
int validity = 0;
int c, isLetter, value, validDigitStruct, validDigit, validLetterStruct, validLetter;
for (int i = 0, hexOffset = 0; i < resultLength; i++, hexOffset += 2) {
c = hex [hexOffset];
// check using calculation over bits to see if first char is a letter
// isLetter is zero if it is a digit, 1 if it is a letter (upper & lowercase)
isLetter = (c >> 6) & 1;
// calculate the tuple value using a multiplication to make up the difference between
// a digit character and an alphanumerical character
// minus 1 for the fact that the letters are not zero based
value = ((c & 0xF) + isLetter * (-1 + 10)) << 4;
// check validity of all the other bits
validity |= c >> 7; // changed to >>, maybe not OK, use UInt?
validDigitStruct = (c & 0x30) ^ 0x30;
validDigit = ((c & 0x8) >> 3) * (c & 0x6);
validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);
validLetterStruct = c & 0x18;
validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
validity |= isLetter * (validLetterStruct | validLetter);
// do the same with the lower (less significant) tuple
c = hex [hexOffset + 1];
isLetter = (c >> 6) & 1;
value ^= (c & 0xF) + isLetter * (-1 + 10);
result [i] = (byte)value;
// check validity of all the other bits
validity |= c >> 7; // changed to >>, maybe not OK, use UInt?
validDigitStruct = (c & 0x30) ^ 0x30;
validDigit = ((c & 0x8) >> 3) * (c & 0x6);
validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);
validLetterStruct = c & 0x18;
validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
validity |= isLetter * (validLetterStruct | validLetter);
}
if (validity != 0) {
throw new ArgumentException ("Hexadecimal encoding incorrect for input " + hex);
}
return result;
}
Преобразовано из кода Java.
С точки зрения скорости это кажется лучше, чем что-либо здесь:
public static string ToHexString(byte[] data) {
byte b;
int i, j, k;
int l = data.Length;
char[] r = new char[l * 2];
for (i = 0, j = 0; i < l; ++i) {
b = data[i];
k = b >> 4;
r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
k = b & 15;
r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
}
return new string(r);
}
И для вставки в строку SQL (если вы не используете параметры команды):
public static String ByteArrayToSQLHexString(byte[] Source)
{
return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
Эта версия ByteArrayToHexViaByteManipulation может быть быстрее.
Из моих отчетов:
- ByteArrayToHexViaByteManipulation3: 1,68 средних тиков (более 1000 пробежек), 17,5X
- ByteArrayToHexViaByteManipulation2: 1,73 средних тиков (более 1000 пробежек), 16,9X
- ByteArrayToHexViaByteManipulation: 2,90 средних тиков (более 1000 прогонов), 10,1X
- ByteArrayToHexViaLookupAndShift: 3,22 средних тиков (более 1000 пробежек), 9,1X
...
static private readonly char[] hexAlphabet = new char[] {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; static string ByteArrayToHexViaByteManipulation3(byte[] bytes) { char[] c = new char[bytes.Length * 2]; byte b; for (int i = 0; i < bytes.Length; i++) { b = ((byte)(bytes[i] >> 4)); c[i * 2] = hexAlphabet[b]; b = ((byte)(bytes[i] & 0xF)); c[i * 2 + 1] = hexAlphabet[b]; } return new string(c); }
И я думаю, что это оптимизация:
static private readonly char[] hexAlphabet = new char[]
{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
static string ByteArrayToHexViaByteManipulation4(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
for (int i = 0, ptr = 0; i < bytes.Length; i++, ptr += 2)
{
byte b = bytes[i];
c[ptr] = hexAlphabet[b >> 4];
c[ptr + 1] = hexAlphabet[b & 0xF];
}
return new string(c);
}
Я не получил код, который ты предложил работать, Олипро. hex[i] + hex[i+1]
видимо вернул int
,
Однако я добился определенного успеха, взяв несколько подсказок из кода Waleeds и собрав их вместе. Это ужасно ужасно, но, похоже, работает и работает в 1/3 времени по сравнению с другими, согласно моим тестам (используя механизм тестирования патронов). В зависимости от размера ввода. Переключение вокруг?: S для выделения 0-9 первым, вероятно, приведет к несколько более быстрому результату, так как цифр больше, чем букв.
public static byte[] StringToByteArray2(string hex)
{
byte[] bytes = new byte[hex.Length/2];
int bl = bytes.Length;
for (int i = 0; i < bl; ++i)
{
bytes[i] = (byte)((hex[2 * i] > 'F' ? hex[2 * i] - 0x57 : hex[2 * i] > '9' ? hex[2 * i] - 0x37 : hex[2 * i] - 0x30) << 4);
bytes[i] |= (byte)(hex[2 * i + 1] > 'F' ? hex[2 * i + 1] - 0x57 : hex[2 * i + 1] > '9' ? hex[2 * i + 1] - 0x37 : hex[2 * i + 1] - 0x30);
}
return bytes;
}
Для производительности я бы пошел с раствором drphrozens. Небольшой оптимизацией для декодера может быть использование таблицы для любого символа, чтобы избавиться от "<< 4".
Очевидно, что два вызова метода являются дорогостоящими. Если какая-либо проверка выполняется на входных или выходных данных (может быть CRC, контрольная сумма или что-то еще), if (b == 255)...
может быть пропущен, и, таким образом, также вызывает метод в целом.
С помощью offset++
а также offset
вместо offset
а также offset + 1
может дать некоторое теоретическое преимущество, но я подозреваю, что компилятор справляется с этим лучше, чем я.
private static readonly byte[] LookupTableLow = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static readonly byte[] LookupTableHigh = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static byte LookupLow(char c)
{
var b = LookupTableLow[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
private static byte LookupHigh(char c)
{
var b = LookupTableHigh[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
public static byte ToByte(char[] chars, int offset)
{
return (byte)(LookupHigh(chars[offset++]) | LookupLow(chars[offset]));
}
Это только с моей головы и не было проверено или сравнительно.