




Либо:
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.
отсутствует точка с запятой в строке hex.AppendFormat ("{0: x2}", b)
Вы используете SubString. Разве этот цикл не выделяет ужасное количество строковых объектов?
Честно говоря, до тех пор, пока это не резко снизит производительность, я буду игнорировать это и доверять Runtime и GC, которые позаботятся об этом.
FWIW Я мог бы получить 4-кратное ускорение на моей машине, исключив подстроки. Не могу опубликовать код, потому что я написал это для своего работодателя.
Ваш StringToByteArray () не работает, если у вас нечетное количество шестнадцатеричных символов. Это легко исправить, если заполнить нечетные строки "0" впереди.
Поскольку байт - это два полубайта, любая шестнадцатеричная строка, которая корректно представляет массив байтов, должна иметь четное количество символов. 0 не следует нигде добавлять - его добавление означало бы сделать предположение о недопустимых данных, что потенциально опасно. Во всяком случае, метод StringToByteArray должен генерировать исключение FormatException, если шестнадцатеричная строка содержит нечетное количество символов.
Первый пример возвращает другое значение, чем второй. Кто-нибудь может объяснить почему?
@DavidBoike Хотя я согласен, если вас интересует только математическое представление, можно добавить нули ко всей строке, но вы правы, это почти наверняка плохие данные, потому что вы запрашиваете байты взамен, и байты всегда будут соответствующим образом дополнены.
См. stackoverflow.com/a/14332574/22656 для версии, которая не использует Substring.
@DavidBoike - "F" недопустимая шестнадцатеричная строка? Разве это не то же самое, что "0F"? Таким образом, у вас может быть шестнадцатеричная строка с нечетным количеством символов.
@ 00jt Вы должны сделать предположение, что F == 0F. Либо это то же самое, что и 0F, либо вход был обрезан, и F на самом деле является началом чего-то, чего вы не получили. Это зависит от вашего контекста, чтобы делать эти предположения, но я считаю, что функция общего назначения должна отклонять нечетные символы как недопустимые, а не делать это предположение для вызывающего кода.
@DavidBoike Вопрос не имел НИЧЕГО общего с «как обрабатывать возможно обрезанные значения потока». Он говорит о String. String myValue = 10.ToString («X»); myValue - это «A», а не «0A». Теперь прочтите эту строку обратно в байты, ой, вы ее сломали.
В вопросе прямо указано, что они хотели бы, чтобы он действительно конвертировался обратно. F! = 0F, поэтому вы получите другой результат. Кроме того, делать ненужные предположения - это вообще плохая практика. Ваш пример глупый, вы бы использовали ToString ("X2"). Ваш пример идентичен тому, что если вы используете ToString ("X3"), он не проходит через функции шестнадцатеричного анализа. Конечно, не потому, что вы его нестандартно закодировали.
В .Net Micro Framework нет ни Convert.ToByte, принимающего два аргумента, ни StringReader, было бы здорово увидеть в ответе StringToByteArray без этих аргументов.
var str = System.Text.Encoding.Default.GetString (результат);
@Link указывает на спам. thinksharp.org/hex-string-to-byte-array-converter/
@DavidBoike Я никогда не знал, что такое клев. Ваш комментарий заставил меня задуматься. Спасибо.
StringToByteArray - ужасное название для этой функции. Назовите его EncodeHex или DecodeHex или создайте класс Hex и вставьте методы Encode или Decode. Вы также можете декодировать base64, кодировать как UTF-8 или UTF-16 и называть его StringToByteArray. Где-то нужно вписать часть Hex в название.
желаю, чтобы в нем были данные для примера.
Методы расширения (отказ от ответственности: полностью непроверенный код, BTW ...):
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();
}
}
и т. д. Используйте любой из Три решения Томалака (последний из которых является методом расширения в строке).
Вероятно, вам следует протестировать код, прежде чем предлагать его для ответа на такой вопрос.
Вы можете использовать метод 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 [])
Отвечает только на половину вопроса.
Где вторая часть ответа?
Если вы хотите большей гибкости, чем 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")));
(Последнее из комментария к исходному сообщению.)
Еще короче: String.Concat (Array.ConvertAll (bytes, x => x.ToString ("X2"))
Просто обратите внимание, что хорошая техника maxc требует .net 4.0
Еще короче: String.Concat (bytes.Select (b => b.ToString ("X2"))) [.NET4]
Отвечает только на половину вопроса.
Зачем второму .Net 4? String.Concat находится в .Net 2.0.
эти циклы в стиле 90-х обычно быстрее, но на достаточно незначительную величину, так что в большинстве случаев это не имеет значения. Тем не менее, стоит упомянуть
Я столкнулся с той же проблемой сегодня и наткнулся на этот код:
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 [] Массив в шестнадцатеричную строку (см. Сообщение PZahra). Я немного изменил код, убрав префикс 0x.
Я провел небольшое тестирование производительности кода, и это было почти в восемь раз быстрее, чем при использовании BitConverter.ToString () (самый быстрый согласно сообщению Патриджа).
не говоря уже о том, что при этом используется меньше всего памяти. Никаких промежуточных строк не создается.
Отвечает только на половину вопроса.
Это здорово, потому что работает практически с любой версией NET, включая NETMF. Победитель!
Принятый ответ предоставляет 2 отличных метода HexToByteArray, которые представляют вторую половину вопроса. Решение Waleed отвечает на текущий вопрос о том, как это сделать, не создавая при этом огромное количество строк.
Копирует и перераспределяет новую строку (c) или достаточно ли умен, чтобы знать, когда можно просто обернуть char []?
@PsychoDad, он копирует. Строка должна быть неизменной, в то время как char [] может измениться после создания строки.
@SlyGryphon На самом деле, если вы прокрутите вниз от упомянутого сообщения на форуме, я фактически предоставил другую сторону этого, которая с тех пор была обновлена здесь: stackoverflow.com/a/22158486/278889
И для вставки в строку SQL (если вы не используете параметры команды):
public static String ByteArrayToSQLHexString(byte[] Source)
{
return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
если Source == null или Source.Length == 0 у нас проблема сэр!
Если вы хотите получить "4-кратное увеличение скорости", о котором сообщает wcoenen, то, если это не очевидно: замените hex.Substring(i, 2) на hex[i]+hex[i+1].
Вы также можете пойти дальше и избавиться от i+=2, используя i++ в обоих местах.
Это отличная статья. Мне нравится решение Валида. Я не проводил его через тест патриджа, но, похоже, он довольно быстрый. Мне также нужен был обратный процесс, преобразовывающий шестнадцатеричную строку в массив байтов, поэтому я написал его как отмену решения Валида. Не уверен, что это быстрее оригинального решения Томалака. Опять же, я не запускал обратный процесс через тест патриджа.
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;
}
Этот код предполагает, что шестнадцатеричная строка использует альфа-символы верхнего регистра и взрывается, если шестнадцатеричная строка использует альфа-символы нижнего регистра. Возможно, вы захотите выполнить преобразование входной строки "в верхний регистр" в целях безопасности.
Это проницательное наблюдение Марк. Код был написан для отмены решения Валида. Вызов ToUpper несколько замедлит алгоритм, но позволит ему обрабатывать альфа-символы нижнего регистра.
Convert.ToByte (topChar + bottomChar) можно записать как (byte) (topChar + bottomChar)
Чтобы справиться с обоими случаями без значительного снижения производительности, hexString[i] &= ~0x20;
Есть класс под названием Мыло, который делает именно то, что вы хотите.
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();
}
SoapHexBinary доступен из .NET 1.0 и находится в mscorlib. Несмотря на забавное пространство имен, оно делает именно то, что задан вопросом.
Отличная находка! Обратите внимание, что вам нужно будет дополнить нечетные строки начальным 0 для GetStringToBytes, как и в другом решении.
Вы видели реализацию мысли? У принятого ответа есть лучший ИМХО.
Вы имеете в виду реализацию SoapHexBinary? Если да, то что делает его хуже, чем реализация в принятом ответе?
Интересно увидеть здесь реализацию Mono: github.com/mono/mono/blob/master/mcs/class/corlib/…
В моих тестах (которые я собираюсь бросить в ответ) Mono's impl. примерно на 10% быстрее, чем SoapHexBinary, и в 16 раз медленнее моего ...
SoapHexBinary не поддерживается в .NET Core / .NET Standard ...
Я не получил код, который вы предложили для работы, Олипро. 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;
}
С точки зрения скорости это кажется лучше, чем что-либо здесь:
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);
}
От разработчиков 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();
}
Хотя приведенное выше чисто и компактно, любители производительности будут кричать об этом, используя счетчики. Вы можете получить максимальную производительность с улучшенной версией Оригинальный ответ Томалака:
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
for(int i=0; i < ba.Length; i++) // <-- Use for loop is faster than foreach
hex.Append(ba[i].ToString("X2")); // <-- ToString is faster than AppendFormat
return hex.ToString();
}
Это самая быстрая из всех процедур, которые я когда-либо видел здесь. Не верьте мне на слово ... проверьте производительность каждой подпрограммы и самостоятельно проверьте ее код CIL.
Итератор - не главная проблема этого кода. Вы должны протестировать b.ToSting("X2").
Зачем усложнять? В Visual Studio 2008 это просто:
C#:
string hex = BitConverter.ToString(YourByteArray).Replace("-", "");
VB:
Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
причина в производительности, когда вам нужно высокопроизводительное решение. :)
Если производительность имеет значение, вот оптимизированное решение:
static readonly char[] _hexDigits = "0123456789abcdef".ToCharArray();
public static string ToHexString(this byte[] bytes)
{
char[] digits = new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
int d1, d2;
d1 = Math.DivRem(bytes[i], 16, out d2);
digits[2 * i] = _hexDigits[d1];
digits[2 * i + 1] = _hexDigits[d2];
}
return new string(digits);
}
Это примерно в 2,5 раза быстрее, чем BitConverter.ToString, и примерно в 7 раз быстрее, чем BitConverter.ToString + удаление символов «-».
Если бы производительность имела значение, вы бы не использовали Math.DivRem для разделения байта на два полубайта.
@dolmen, вы запускали тесты производительности с Math.DivRem и без? Я серьезно сомневаюсь, что это влияет на производительность Любые: реализация Math.DivRem - это именно то, что вы делали бы вручную, и метод очень прост, поэтому он всегда встроен JIT (на самом деле он предназначен для встроенного, как предполагает примененный атрибут TargetedPatchingOptOut к нему)
@ThomasLevesque Реализация DivRem выполняет модульную операцию и деление. Почему вы думаете, что эти операции выполняются вручную? Для меня естественной реализацией является github.com/patridge/PerformanceStubs/blob/master/…, которая выполняет битовый сдвиг и логическое и. Эти операции намного дешевле модуля и деления даже на современных процессорах.
Эту проблему также можно решить с помощью справочной таблицы. Это потребует небольшого количества статической памяти как для кодировщика, так и для декодера. Однако этот метод будет быстрым:
В моем решении для таблицы кодирования используется 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). Должны быть реализованы методы де / кодирования потоков или массивов, это просто доказательство концепции.
Использование памяти в 256 байт незначительно при запуске кода в среде CLR.
Для производительности я бы выбрал решение 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]));
}
Это просто не в моей голове и не тестировалось и не тестировалось.
Еще один вариант разнообразия:
public static byte[] FromHexString(string src)
{
if (String.IsNullOrEmpty(src))
return null;
int index = src.Length;
int sz = index / 2;
if (sz <= 0)
return null;
byte[] rc = new byte[sz];
while (--sz >= 0)
{
char lo = src[--index];
char hi = src[--index];
rc[sz] = (byte)(
(
(hi >= '0' && hi <= '9') ? hi - '0' :
(hi >= 'a' && hi <= 'f') ? hi - 'a' + 10 :
(hi >= 'A' && hi <= 'F') ? hi - 'A' + 10 :
0
)
<< 4 |
(
(lo >= '0' && lo <= '9') ? lo - '0' :
(lo >= 'a' && lo <= 'f') ? lo - 'a' + 10 :
(lo >= 'A' && lo <= 'F') ? lo - 'A' + 10 :
0
)
);
}
return rc;
}
Я подозреваю, что скорость этого сбивает с толку большинство других тестов ...
Public Function BufToHex(ByVal buf() As Byte) As String
Dim sB As New System.Text.StringBuilder
For i As Integer = 0 To buf.Length - 1
sB.Append(buf(i).ToString("x2"))
Next i
Return sB.ToString
End Function
Что заставляет вас думать, что? Вы создаете новый строковый объект для каждого байта в буфере и не изменяете размер конструктора строк (что может привести к многократному изменению размера буфера на больших массивах).
Простое преобразование байтов в английский язык :)
Чтобы не наваливаться на многие ответы здесь, но я нашел довольно оптимальную (в ~ 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 от символа к полубайту другим методом, но это был самый быстрый способ.
(Я признаю, что это отвечает на половину вопроса. Я чувствовал, что преобразование строка-> byte [] было недостаточно представлено, в то время как угол byte [] -> строка, кажется, хорошо покрыт. Таким образом, этот ответ.)
Для последователей Кнута: я сделал это, потому что мне нужно анализировать несколько тысяч шестнадцатеричных строк каждые несколько минут или около того, поэтому важно, чтобы это было как можно быстрее (во внутреннем цикле, как это было). Решение Tomalak не заметно медленнее, если не выполняется много таких синтаксических анализов.
Это работает, чтобы перейти от строки к массиву байтов ...
public static byte[] StrToByteArray(string str)
{
Dictionary<string, byte> hexindex = new Dictionary<string, byte>();
for (byte i = 0; i < 255; i++)
hexindex.Add(i.ToString("X2"), i);
List<byte> hexres = new List<byte>();
for (int i = 0; i < str.Length; i += 2)
hexres.Add(hexindex[str.Substring(i, 2)]);
return hexres.ToArray();
}
Думаю, его скорость стоит 16 дополнительных байтов.
static char[] hexes = new char[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
public static string ToHexadecimal (this byte[] Bytes)
{
char[] Result = new char[Bytes.Length << 1];
int Offset = 0;
for (int i = 0; i != Bytes.Length; i++) {
Result[Offset++] = hexes[Bytes[i] >> 4];
Result[Offset++] = hexes[Bytes[i] & 0x0F];
}
return new string(Result);
}
На самом деле это медленнее, чем другие подходы, основанные на поиске в таблице (по крайней мере, в моих тестах). Использование != вместо < нарушает некоторые шаблоны оптимизации JIT, а дополнительный счетчик для Offset также кажется дорогостоящим.
При написании криптографического кода обычно избегают ветвлений, зависящих от данных, и поиска в таблицах, чтобы гарантировать, что время выполнения не зависит от данных, поскольку время, зависящее от данных, может привести к атакам по побочным каналам.
К тому же это довольно быстро.
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
Abandon all hope, ye who enter here
Объяснение странной возни с битами:
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.(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, начиная с (0 & -7) == 0 и (-1 & -7) == -7.Некоторые дополнительные соображения:
c, поскольку измерения показывают, что вычисление ее из i дешевле.i < bytes.Length в качестве верхней границы цикла позволяет JITter исключить проверки границ на bytes[i], поэтому я выбрал этот вариант.b в int допускает ненужные преобразования из и в байты.А hex string к byte[] array?
+1 за правильное цитирование вашего источника после того, как вы применили эту черную магию. Приветствую Ктулху.
Лучший ответ (для части вопроса с шестнадцатеричным кодированием)!
А как насчет строки в байт []?
Я хотел сказать, что если у меня "0x1B", как мне преобразовать его в байт?
Хороший! Для тех, кому нужен вывод в нижнем регистре, выражение, очевидно, меняется на 87 + b + (((b-10)>>31)&-39)
Теперь у меня есть его на Java и C# как для кодирования, так и для декодирования, чтобы продемонстрировать мою «темную магию» (одиночный цикл, без ветвей, кроме последней для шестнадцатеричных ошибок). Конечно, помимо вашего краткого кода, я люблю иногда ломать голову.
@AaA вы хотите преобразовать его в массив байтовых массивов? ;)
@CoolOppo, я не уверен, что вы имеете в виду, но шестнадцатеричная строка находится в формате "123456789ABCDEF", что означает, что каждые два символа преобразуются в one byte
@AaA Вы сказали "byte[] array", что буквально означает массив байтовых массивов или byte[][]. Я просто подшучивал.
Почему бы просто не использовать 'A'-0xA вместо 55? Пусть компилятор во всем разберется; магические числа, такие как 55, трудны для понимания людьми и подвержены ошибкам.
@DavidRTribble Потому что я не хочу брать на себя венок Великого Лорда Ктулху.
@CodesInChaos - я знаю, вы имели в виду гнев. Но видите ли, это проблема с символом 101, который я, конечно, записал бы как 'e'. ;-)
@CodesInChaos Удивительно, ни разу, хотя этот минус не играет в такую игру с оператором 'and', никогда не использовал его. Похоже на волшебство. Что такое фон? По моему мнению, минус в таком выражении должен означать, что установлен крайний левый бит, а обычное 'and' должно давать минус, если источник только минус, но он другой: 3 & 9 == 1, все в порядке, но 3 & -9 = = 3 и (-3) & (-9) == -11, какого хрена?
Можно ли здесь использовать (ReadOnly) Span?
Два мэшапа, которые объединяют две операции полубайта в одну.
Наверное, довольно эффективная версия:
public static string ByteArrayToString2(byte[] ba)
{
char[] c = new char[ba.Length * 2];
for( int i = 0; i < ba.Length * 2; ++i)
{
byte b = (byte)((ba[i>>1] >> 4*((i&1)^1)) & 0xF);
c[i] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string( c );
}
Декадентская версия linq-with-bit-hacking:
public static string ByteArrayToString(byte[] ba)
{
return string.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) );
}
И наоборот:
public static byte[] HexStringToByteArray( string s )
{
byte[] ab = new byte[s.Length>>1];
for( int i = 0; i < s.Length; i++ )
{
int b = s[i];
b = (b - '0') + ((('9' - b)>>31)&-7);
ab[i>>1] |= (byte)(b << 4*((i&1)^1));
}
return ab;
}
HexStringToByteArray ("09") возвращает 0x02, что плохо.
Вот мой шанс. Я создал пару классов расширения для расширения строки и байта. В тесте с большими файлами производительность сопоставима с Byte Manipulation 2.
Приведенный ниже код для ToHexString - это оптимизированная реализация алгоритма поиска и сдвига. Он почти идентичен таковому от Behrooz, но оказалось, что для итерации используется foreach, а счетчик работает быстрее, чем явно индексирующий for.
На моей машине он занимает 2-е место после Byte Manipulation 2 и представляет собой очень читаемый код. Также представляют интерес следующие результаты тестов:
ToHexStringCharArrayWithCharArrayLookup: 41589,69 средних тиков (более 1000 прогонов), 1,5X ToHexStringCharArrayWithStringLookup: 50 764,06 средних тиков (более 1000 прогонов), 1,2X ToHexStringStringBuilderWithCharArrayLookup: 62812,87 средних тиков (более 1000 прогонов), 1,0X
На основании приведенных выше результатов можно с уверенностью заключить, что:
Вот код:
using System;
namespace ConversionExtensions
{
public static class ByteArrayExtensions
{
private readonly static char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
public static string ToHexString(this byte[] bytes)
{
char[] hex = new char[bytes.Length * 2];
int index = 0;
foreach (byte b in bytes)
{
hex[index++] = digits[b >> 4];
hex[index++] = digits[b & 0x0F];
}
return new string(hex);
}
}
}
using System;
using System.IO;
namespace ConversionExtensions
{
public static class StringExtensions
{
public static byte[] ToBytes(this string hexString)
{
if (!string.IsNullOrEmpty(hexString) && hexString.Length % 2 != 0)
{
throw new FormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");
}
hexString = hexString.ToUpperInvariant();
byte[] data = new byte[hexString.Length / 2];
for (int index = 0; index < hexString.Length; index += 2)
{
int highDigitValue = hexString[index] <= '9' ? hexString[index] - '0' : hexString[index] - 'A' + 10;
int lowDigitValue = hexString[index + 1] <= '9' ? hexString[index + 1] - '0' : hexString[index + 1] - 'A' + 10;
if (highDigitValue < 0 || lowDigitValue < 0 || highDigitValue > 15 || lowDigitValue > 15)
{
throw new FormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");
}
else
{
byte value = (byte)((highDigitValue << 4) | (lowDigitValue & 0x0F));
data[index / 2] = value;
}
}
return data;
}
}
}
Ниже приведены результаты тестирования, которые я получил, когда поместил свой код в проект тестирования @patridge на своей машине. Я также добавил тест на преобразование в массив байтов из шестнадцатеричного. Тестовые прогоны, в которых использовался мой код, - это ByteArrayToHexViaOptimizedLookupAndShift и HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte был взят из XXXX. HexToByteArrayViaSoapHexBinary - это тот, который был из ответа @Mykroft.
Intel Pentium III Xeon processor
Cores: 4 <br/> Current Clock Speed: 1576 <br/> Max Clock Speed: 3092 <br/>Converting array of bytes into hexadecimal string representation
ByteArrayToHexViaByteManipulation2: 39,366.64 average ticks (over 1000 runs), 22.4X
ByteArrayToHexViaOptimizedLookupAndShift: 41,588.64 average ticks (over 1000 runs), 21.2X
ByteArrayToHexViaLookup: 55,509.56 average ticks (over 1000 runs), 15.9X
ByteArrayToHexViaByteManipulation: 65,349.12 average ticks (over 1000 runs), 13.5X
ByteArrayToHexViaLookupAndShift: 86,926.87 average ticks (over 1000 runs), 10.2X
ByteArrayToHexStringViaBitConverter: 139,353.73 average ticks (over 1000 runs),6.3X
ByteArrayToHexViaSoapHexBinary: 314,598.77 average ticks (over 1000 runs), 2.8X
ByteArrayToHexStringViaStringBuilderForEachByteToString: 344,264.63 average ticks (over 1000 runs), 2.6X
ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382,623.44 average ticks (over 1000 runs), 2.3X
ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818,111.95 average ticks (over 1000 runs), 1.1X
ByteArrayToHexStringViaStringConcatArrayConvertAll: 839,244.84 average ticks (over 1000 runs), 1.1X
ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867,303.98 average ticks (over 1000 runs), 1.0X
ByteArrayToHexStringViaStringJoinArrayConvertAll: 882,710.28 average ticks (over 1000 runs), 1.0X
Дополнение к ответу от @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).
Для "A" ... "F" это hi = ch - 65 + 10; (это из-за 0x00000000 & 7).
Для 'a' .. 'f' нам нужны большие числа, поэтому мы должны вычесть 32 из версии по умолчанию, сделав несколько битов 0 с помощью & 0x0f.
65 - это код для 'A'
48 - это код для '0'
7 - количество букв между '9' и 'A' в таблице ASCII (...456789:;<=>?@ABCD...).
Эта версия ByteArrayToHexViaByteManipulation могла бы быть быстрее.
Из моих отчетов:
...
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);
}
Не оптимизирован для скорости, но больше LINQy, чем большинство ответов (.NET 4.0):
<Extension()>
Public Function FromHexToByteArray(hex As String) As Byte()
hex = If(hex, String.Empty)
If hex.Length Mod 2 = 1 Then hex = "0" & hex
Return Enumerable.Range(0, hex.Length \ 2).Select(Function(i) Convert.ToByte(hex.Substring(i * 2, 2), 16)).ToArray
End Function
<Extension()>
Public Function ToHexString(bytes As IEnumerable(Of Byte)) As String
Return String.Concat(bytes.Select(Function(b) b.ToString("X2")))
End Function
Еще одна быстрая функция ...
private static readonly byte[] HexNibble = new byte[] {
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
};
public static byte[] HexStringToByteArray( string str )
{
int byteCount = str.Length >> 1;
byte[] result = new byte[byteCount + (str.Length & 1)];
for( int i = 0; i < byteCount; i++ )
result[i] = (byte) (HexNibble[str[i << 1] - 48] << 4 | HexNibble[str[(i << 1) + 1] - 48]);
if ( (str.Length & 1) != 0 )
result[byteCount] = (byte) HexNibble[str[str.Length - 1] - 48];
return result;
}
Также есть XmlWriter.WriteBinHex (см. MSDN страница). Это очень полезно, если вам нужно поместить шестнадцатеричную строку в поток XML.
Вот автономный метод, чтобы увидеть, как это работает:
public static string ToBinHex(byte[] bytes)
{
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
xmlWriterSettings.ConformanceLevel = ConformanceLevel.Fragment;
xmlWriterSettings.CheckCharacters = false;
xmlWriterSettings.Encoding = ASCIIEncoding.ASCII;
MemoryStream memoryStream = new MemoryStream();
using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, xmlWriterSettings))
{
xmlWriter.WriteBinHex(bytes, 0, bytes.Length);
}
return Encoding.ASCII.GetString(memoryStream.ToArray());
}
Безопасные версии:
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;
}
}
}
Кстати При тестировании производительности при инициализации алфавита каждый раз, когда вызывается неправильная функция convert, алфавит должен быть константным (для строки) или статическим только для чтения (для char []). Тогда преобразование байта [] в строку на основе алфавита становится таким же быстрым, как и версии с манипуляциями с байтами.
И, конечно же, тест должен быть скомпилирован в Release (с оптимизацией) и с отключенной опцией отладки «Подавить JIT-оптимизацию» (то же самое для «Включить только мой код», если код должен быть отлаживаемым).
Я приму участие в этом соревновании по игре с битами, так как у меня есть ответ, в котором также используется битовая игра в шестнадцатеричные числа расшифровать. Обратите внимание, что использование символьных массивов может быть даже быстрее, поскольку вызов методов 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.
Хм, мне действительно нужно оптимизировать это для Char[] и использовать Char внутри вместо целых чисел ...
Для C# инициализация переменных там, где они используются, а не вне цикла, вероятно, предпочтительнее, чтобы позволить компилятору оптимизировать. В любом случае я получаю одинаковую производительность
Другой подход, основанный на поисковой таблице. Здесь используется только одна таблица поиска для каждого байта вместо таблицы поиска на полубайт.
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;
}
Почему создание таблицы поиска в небезопасной версии меняет местами полубайты предварительно вычисленного байта? Я думал, что порядок байтов меняет только порядок объектов, которые были сформированы из нескольких байтов.
@RaifAtef Здесь важен не порядок кусочков. Но порядок 16-битных слов в 32-битном целом числе. Но я подумываю переписать его, чтобы тот же код мог работать независимо от порядка байтов.
Перечитывая код, я думаю, что вы сделали это, потому что, когда вы позже преобразуете char * в uint * и назначите его (при генерации шестнадцатеричного символа), среда выполнения / ЦП перевернут байты (поскольку uint не обрабатывается так же, как 2 отдельных 16-битных символа), поэтому вы предварительно переворачиваете их для компенсации. Я прав ? Порядок байтов сбивает с толку :-).
Хорошо, я укушу - какое преимущество есть в том, чтобы закрепить _lookup32Unsafe на неопределенный срок вместо того, чтобы просто выполнять третий оператор fixed и позволять GC перемещать массив в его основное содержимое, когда этот метод не работает?
@JoeAmenta Не уверен, есть ли в этом случае какое-либо измеримое преимущество. Возможно, я просто не думал об этой альтернативе при написании этого кода.
Это просто ответ на половину вопроса ... Как насчет того, чтобы от шестнадцатеричной строки к байтам?
Почему только небезопасный вариант проверяет порядок байтов? Первый пример, кажется, просто предполагает, что машина littleEndian.
@TamaMcGlinn В безопасной реализации для внутреннего представления используется прямой порядок байтов, но не делается никаких предположений относительно машинного порядка байтов. Так что он все равно должен работать на машинах с прямым порядком байтов (возможно, немного медленнее). В отличие от этого небезопасный вариант повторно интерпретирует два 16-битных слова как одно 32-битное слово, поэтому он должен быть осторожен с порядком байтов хоста.
@CodesInChaos Интересно, можно ли теперь использовать Span вместо unsafe ??
@Konrad, наверное, мог бы; Интересно то, что в этот момент static ReadOnlySpan, инициализированный массивом литералов, становится статическими данными в DLL.
Следующее расширяет отличный ответ здесь, позволяя также использовать собственный вариант нижнего регистра, а также обрабатывает нулевой или пустой ввод и делает это методом расширения.
/// <summary>
/// Converts the byte array to a hex string very fast. Excellent job
/// with code lightly adapted from 'community wiki' here: https://stackoverflow.com/a/14333437/264031
/// (the function was originally named: ByteToHexBitFiddle). Now allows a native lowerCase option
/// to be input and allows null or empty inputs (null returns null, empty returns empty).
/// </summary>
public static string ToHexString(this byte[] bytes, bool lowerCase = false)
{
if (bytes == null)
return null;
else if (bytes.Length == 0)
return "";
char[] c = new char[bytes.Length * 2];
int b;
int xAddToAlpha = lowerCase ? 87 : 55;
int xAddToDigit = lowerCase ? -39 : -7;
for (int i = 0; i < bytes.Length; i++) {
b = bytes[i] >> 4;
c[i * 2] = (char)(xAddToAlpha + b + (((b - 10) >> 31) & xAddToDigit));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(xAddToAlpha + b + (((b - 10) >> 31) & xAddToDigit));
}
string val = new string(c);
return val;
}
public static string ToHexString(this IEnumerable<byte> bytes, bool lowerCase = false)
{
if (bytes == null)
return null;
byte[] arr = bytes.ToArray();
return arr.ToHexString(lowerCase);
}
Обратная функция для кода Валида Эйссы (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);
}
Другой способ - использовать stackalloc для уменьшения нагрузки на память GC:
static string ByteToHexBitFiddle(byte[] bytes)
{
var c = stackalloc char[bytes.Length * 2 + 1];
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));
}
c[bytes.Length * 2 ] = '\0';
return new string(c);
}
static string ByteArrayToHexViaLookupPerByte2(byte[] bytes)
{
var result3 = new uint[bytes.Length];
for (int i = 0; i < bytes.Length; i++)
result3[i] = _Lookup32[bytes[i]];
var handle = GCHandle.Alloc(result3, GCHandleType.Pinned);
try
{
var result = Marshal.PtrToStringUni(handle.AddrOfPinnedObject(), bytes.Length * 2);
return result;
}
finally
{
handle.Free();
}
}
Эта функция в моих тестах всегда является второй записью после небезопасной реализации.
К сожалению, тестовый стенд не так надежен ... если вы запустите его несколько раз, список будет перетасован настолько, что кто знает, что после небезопасного действительно самое быстрое! Он не учитывает предварительное нагревание, время jit-компиляции и снижение производительности сборщика мусора. Я хотел бы переписать его, чтобы получить больше информации, но у меня не было на это времени.
Базовое решение с поддержкой расширений
public static class Utils
{
public static byte[] ToBin(this 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;
}
public static string ToHex(this byte[] ba)
{
return BitConverter.ToString(ba).Replace("-", "");
}
}
И используйте этот класс, как показано ниже
byte[] arr1 = new byte[] { 1, 2, 3 };
string hex1 = arr1.ToHex();
byte[] arr2 = hex1.ToBin();
Я придумал другой код, устойчивый к лишним символам (пробелы, тире ...). Он в основном основан на некоторых достаточно быстрых ответах здесь. Это позволяет разобрать следующий "файл"
00-aa-84-fb
12 32 FF CD
12 00
12_32_FF_CD
1200d5e68a
/// <summary>Reads a hex string into bytes</summary>
public static IEnumerable<byte> HexadecimalStringToBytes(string hex) {
if (hex == null)
throw new ArgumentNullException(nameof(hex));
char c, c1 = default(char);
bool hasc1 = false;
unchecked {
for (int i = 0; i < hex.Length; i++) {
c = hex[i];
bool isValid = 'A' <= c && c <= 'f' || 'a' <= c && c <= 'f' || '0' <= c && c <= '9';
if (!hasc1) {
if (isValid) {
hasc1 = true;
}
} else {
hasc1 = false;
if (isValid) {
yield return (byte)((GetHexVal(c1) << 4) + GetHexVal(c));
}
}
c1 = c;
}
}
}
/// <summary>Reads a hex string into a byte array</summary>
public static byte[] HexadecimalStringToByteArray(string hex)
{
if (hex == null)
throw new ArgumentNullException(nameof(hex));
var bytes = new List<byte>(hex.Length / 2);
foreach (var item in HexadecimalStringToBytes(hex)) {
bytes.Add(item);
}
return bytes.ToArray();
}
private static byte GetHexVal(char val)
{
return (byte)(val - (val < 0x3A ? 0x30 : val < 0x5B ? 0x37 : 0x57));
// ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^
// digits 0-9 upper char A-Z a-z
}
При копировании обращайтесь к полный код. Включены юнит-тесты.
Некоторые могут сказать, что это слишком терпимо к лишним символам. Поэтому не полагайтесь на этот код для выполнения проверки (или ее изменения).
// a safe version of the lookup solution:
public static string ByteArrayToHexViaLookup32Safe(byte[] bytes, bool withZeroX)
{
if (bytes.Length == 0)
{
return withZeroX ? "0x" : "";
}
int length = bytes.Length * 2 + (withZeroX ? 2 : 0);
StateSmall stateToPass = new StateSmall(bytes, withZeroX);
return string.Create(length, stateToPass, (chars, state) =>
{
int offset0x = 0;
if (state.WithZeroX)
{
chars[0] = '0';
chars[1] = 'x';
offset0x += 2;
}
Span<uint> charsAsInts = MemoryMarshal.Cast<char, uint>(chars.Slice(offset0x));
int targetLength = state.Bytes.Length;
for (int i = 0; i < targetLength; i += 1)
{
uint val = Lookup32[state.Bytes[i]];
charsAsInts[i] = val;
}
});
}
private struct StateSmall
{
public StateSmall(byte[] bytes, bool withZeroX)
{
Bytes = bytes;
WithZeroX = withZeroX;
}
public byte[] Bytes;
public bool WithZeroX;
}
Кратчайший способ и поддерживаемое ядро .net:
public static string BytesToString(byte[] ba) =>
ba.Aggregate(new StringBuilder(32), (sb, b) => sb.Append(b.ToString("X2"))).ToString();
Это прекрасно, если размер ba составляет 16 байт или меньше. Но он должен быть new StringBuilder(ba.Length * 2), чтобы эффективно обрабатывать массив байтов любой длины.
Существует еще не упомянутое простое однострочное решение, которое преобразует шестнадцатеричные строки в байтовые массивы (здесь нас не волнует отрицательная интерпретация, поскольку это не имеет значения):
BigInteger.Parse(str, System.Globalization.NumberStyles.HexNumber).ToByteArray().Reverse().ToArray();
Это не сохраняет начальные байты 0. Например, строка "000080" приводит к однобайтовому массиву { 0x80 }, а не к ожидаемому трехбайтовому массиву { 0x00, 0x00, 0x80 }.
Да, я полагаю, что что-то вроде Enumerable.Repeat <byte> (0, (len (str) / 2 - len (bigIntBytes)). Concat (bigIntBytes) .ToArray () необходимо в этом случае
Вместо того, чтобы переворачивать массив, вы можете сделать массив в режиме прямого байта: .ToByteArray(isBigEndian: true)
Спасибо, Симон, я не знал или хотя бы забыл об этом удобном параметре!
С Java 8 мы можем использовать Byte.toUnsignedInt
public static String convertBytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte byt : bytes) {
int decimal = Byte.toUnsignedInt(byt);
String hex = Integer.toHexString(decimal);
result.append(hex);
}
return result.toString();
}
Неправильно, toHexString может возвращать только один символ вместо двух.
Для удобства копирования и вставки я объединил несколько ответов в класс:
/// <summary>
/// Extension methods to quickly convert byte array to string and back.
/// </summary>
public static class HexConverter
{
/// <summary>
/// Map values to hex digits
/// </summary>
private static readonly char[] HexDigits =
{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
/// <summary>
/// Map 56 characters between ['0', 'F'] to their hex equivalents, and set invalid characters
/// such that they will overflow byte to fail conversion.
/// </summary>
private static readonly ushort[] HexValues =
{
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x000A, 0x000B,
0x000C, 0x000D, 0x000E, 0x000F
};
/// <summary>
/// Empty byte array
/// </summary>
private static readonly byte[] Empty = new byte[0];
/// <summary>
/// Convert a byte array to a hexadecimal string.
/// </summary>
/// <param name = "bytes">
/// The input byte array.
/// </param>
/// <returns>
/// A string of hexadecimal digits.
/// </returns>
public static string ToHexString(this byte[] bytes)
{
var c = new char[bytes.Length * 2];
for (int i = 0, j = 0; i < bytes.Length; i++)
{
c[j++] = HexDigits[bytes[i] >> 4];
c[j++] = HexDigits[bytes[i] & 0x0F];
}
return new string(c);
}
/// <summary>
/// Parse a string of hexadecimal digits into a byte array.
/// </summary>
/// <param name = "hexadecimalString">
/// The hexadecimal string.
/// </param>
/// <returns>
/// The parsed <see cref = "byte[]"/> array.
/// </returns>
/// <exception cref = "ArgumentException">
/// The input string either contained invalid characters, or was of an odd length.
/// </exception>
public static byte[] ToByteArray(string hexadecimalString)
{
if (!TryParse(hexadecimalString, out var value))
{
throw new ArgumentException("Invalid hexadecimal string", nameof(hexadecimalString));
}
return value;
}
/// <summary>
/// Parse a hexadecimal string to bytes
/// </summary>
/// <param name = "hexadecimalString">
/// The hexadecimal string, which must be an even number of characters.
/// </param>
/// <param name = "value">
/// The parsed value if successful.
/// </param>
/// <returns>
/// True if successful.
/// </returns>
public static bool TryParse(string hexadecimalString, out byte[] value)
{
if (hexadecimalString.Length == 0)
{
value = Empty;
return true;
}
if (hexadecimalString.Length % 2 != 0)
{
value = Empty;
return false;
}
try
{
value = new byte[hexadecimalString.Length / 2];
for (int i = 0, j = 0; j < hexadecimalString.Length; i++)
{
value[i] = (byte)((HexValues[hexadecimalString[j++] - '0'] << 4)
| HexValues[hexadecimalString[j++] - '0']);
}
return true;
}
catch (OverflowException)
{
value = Empty;
return false;
}
}
}
Самый быстрый способ для людей старой школы ... скучаю по указателям
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
Принятый ответ ниже, похоже, выделяет ужасное количество строк в преобразовании строки в байты. Мне интересно, как это влияет на производительность