Как преобразовать массив байтов в шестнадцатеричную строку и наоборот?
Как вы можете преобразовать массив байтов в шестнадцатеричную строку и наоборот?
Ответы
Ответ 1
Или:
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
.
Ответ 2
Анализ производительности
Примечание: новый лидер по состоянию на 2015-08-20.
Я запускал каждый из различных методов преобразования с помощью некоторого грубого тестирования производительности Stopwatch
, пробега со случайным предложением (n = 61, 1000 итераций) и пробега с текстом Project Gutenburg (n = 1,238,957, 150 итераций). Вот результаты, от самого быстрого до самого медленного. Все измерения находятся в тиках (10 000 тиков = 1 мс), а все относительные ноты сравниваются с [самой медленной] реализацией StringBuilder
. Для используемого кода см. Ниже или репозиторий тестовой среды, где я теперь поддерживаю код для его запуска.
отказ
ПРЕДУПРЕЖДЕНИЕ: не полагайтесь на эту статистику для чего-либо конкретного; это просто образец пробных данных. Если вам действительно нужна первоклассная производительность, пожалуйста, проверьте эти методы в среде, представляющей ваши производственные потребности, с данными, представляющими, что вы будете использовать.
Результаты
- Поиск по байтам
unsafe
(через CodesInChaos) (добавлен в тестовое репо с помощью airbreather) - Текст: 4 727,85 (105,2X)
- Приговор: 0,28 (99,7X)
- Поиск по байтам (через CodesInChaos)
- Текст: 10 853,96 (45,8X быстрее)
- Предложение: 0,65 (на 42,7 раза быстрее)
- Манипуляция байтов 2 (через CodesInChaos)
- Текст: 12 967,69 (38,4 раза быстрее)
- Предложение: 0,73 (37,9 раза быстрее)
- Манипуляция байтами (через Waleed Eissa)
- Текст: 16 856,64 (на 29,5 раза быстрее)
- Предложение: 0.70 (на 39.5X быстрее)
- Поиск/смена (через Натана Моинвазири)
- Текст: 23,201,23 (21,4 раза быстрее)
- Предложение: 1,24 (на 22,3 раза быстрее)
- Поиск по грызуну (через Брайана Ламберта)
- Текст: 23 879,41 (на 20,8 раза быстрее)
- Приговор: 1,15 (на 23,9 раза быстрее)
-
BitConverter
(через Tomalak) - Текст: 113 269,34 (4,4 раза быстрее)
- Предложение: 9,98 (2,8 раза быстрее)
-
{SoapHexBinary}.ToString
(через Mykroft) - Текст: 178,601.39 (2.8X быстрее)
- Предложение: 10,68 (2,6 раза быстрее)
-
{byte}.ToString("X2")
(используя foreach
) (полученный от Will Dean) - Текст: 308 805,38 (на 2,4 раза быстрее)
- Предложение: 16,89 (2,4 раза быстрее)
-
{byte}.ToString("X2")
(используя {IEnumerable}.Aggregate
, требуется System.Linq) (через Mark) - Текст: 352 828,20 (на 2,1 раза быстрее)
- Предложение: 16,87 (2,4 раза быстрее)
-
Array.ConvertAll
(используя string.Join
) (через Will Dean) - Текст: 675 451,57 (на 1,1 раза быстрее)
- Приговор: 17,95 (2,2 раза быстрее)
-
Array.ConvertAll
(используя string.Concat
, требуется.NET 4.0) (через Will Dean) - Текст: 752 078,70 (на 1,0X быстрее)
- Предложение: 18,28 (2,2 раза быстрее)
-
{StringBuilder}.AppendFormat
(используя foreach
) (через Tomalak) - Текст: 672,115.77 (на 1,1 раза быстрее)
- Предложение: 36,82 (1,1 раза быстрее)
-
{StringBuilder}.AppendFormat
(используя {IEnumerable}.Aggregate
, требуется System.Linq) (полученный от ответа Томалака) - Текст: 718,380.63 (на 1.0X быстрее)
- Предложение: 39,71 (1,0X быстрее)
Таблицы поиска взяли на себя инициативу по манипулированию байтами. В принципе, существует некоторая форма предварительного вычисления того, что любой данный кусок или байт будет в шестнадцатеричном виде. Затем, когда вы копируете данные, вы просто просматриваете следующую часть, чтобы увидеть, какую шестую строку она будет. Это значение затем добавляется к результирующему выводу строки некоторым способом. В течение длительного времени манипулирование байтами, которое потенциально труднее было прочесть некоторыми разработчиками, было наиболее эффективным.
Ваш лучший выбор - это найти некоторые репрезентативные данные и попробовать их в производственной среде. Если у вас разные ограничения памяти, вы можете предпочесть метод с меньшим количеством распределений, который будет быстрее, но потребляет больше памяти.
Тестирование кода
Не стесняйтесь играть с кодом тестирования, который я использовал. Версия здесь включена, но вы можете клонировать репо и добавлять свои собственные методы. Отправьте запрос на перенос, если найдете что-нибудь интересное или хотите улучшить структуру тестирования, которую он использует.
- Добавьте новый статический метод (
Func<byte[], string>
) в /Tests/ConvertByteArrayToHexString/Test.cs. - Добавьте это имя метода в возвращаемое значение
TestCandidates
в том же классе. - Убедитесь, что вы используете нужную версию ввода, предложение или текст, переключая комментарии в
GenerateTestInput
в том же классе. - Нажмите F5 и дождитесь выхода (дамп HTML также создается в папке /bin).
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)
Добавлен Waleed ответ на анализ. Довольно быстро.
Обновление (2011-10-05)
Добавлен вариант string.Concat
Array.ConvertAll
для полноты (требуется.NET 4.0). string.Join
с string.Join
версия.
Обновление (2012-02-05)
Test repo включает в себя больше вариантов, таких как StringBuilder.Append(b.ToString("X2"))
. Ничего не расстраивало результаты. foreach
быстрее, чем {IEnumerable}.Aggregate
, например, но BitConverter
все еще выигрывает.
Обновление (2012-04-03)
Добавлен ответ Mykroft SoapHexBinary
на анализ, который занял третье место.
Обновление (2013-01-15)
Добавлен ответ манипуляции байтами CodesInChaos, который занял первое место (с большим отрывом на больших блоках текста).
Обновление (2013-05-23)
Добавлен ответ Найтана Моинвазири и вариант из блога Брайана Ламберта. Оба довольно быстрые, но не лидирующие на тестовой машине, которую я использовал (AMD Phenom 9750).
Обновление (2014-07-31)
Добавлено @CodesInChaos новый байт-ответ поиска. Похоже, что он возглавил как тесты предложений, так и полнотекстовые тесты.
Обновление (2015-08-20)
Добавлена оптимизация airbreather и unsafe
вариант для ответа на репо. Если вы хотите играть в небезопасной игре, вы можете получить огромный прирост производительности по сравнению с предыдущими победителями как коротких строк, так и больших текстов.
Ответ 3
Там класс называется 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();
}
Ответ 4
При написании криптокода это часто встречается, чтобы избежать зависящих от данных ветвей и поиска таблиц, чтобы гарантировать, что время выполнения не зависит от данных, поскольку время, зависящее от данных, может привести к атакам в боковых каналах.
Это также довольно быстро.
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 позволяет ненужные преобразования из и в байты.
Ответ 5
Если вы хотите больше гибкости, чем 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")));
(Последний из комментария к исходному сообщению.)
Ответ 6
Другой подход на основе таблицы поиска. В этом случае для каждого байта используется только одна таблица поиска, а не таблица поиска за кусочек.
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
sibling:
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;
}
Ответ 7
Вы можете использовать метод 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 [])
Ответ 8
Сегодня я столкнулся с одной и той же проблемой, и я наткнулся на этот код:
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() (самое быстрое в соответствии с положением patridge).
Ответ 9
Это ответ на пересмотренный вариант 4 популярного ответа Томалака (и последующих изменений).
Я сделаю так, что это изменение неверно, и объясните, почему оно может быть отменено. По пути вы можете узнать кое-что о некоторых внутренних элементах и посмотреть еще один пример того, что такое преждевременная оптимизация и как она может вас укусить.
tl; dr: Просто используйте Convert.ToByte
и String.Substring
если вы спешите ("Исходный код" ниже), это лучшая комбинация, если вы не хотите повторно реализовывать Convert.ToByte
. Используйте что-то более продвинутое (см. Другие ответы), который не использует Convert.ToByte
если вам нужна производительность. Есть ли что - нибудь еще, кроме не использовать String.Substring
в сочетании с Convert.ToByte
, если кто - то есть что - то интересное, чтобы сказать об этом в комментариях этого ответа.
warning: этот ответ может устареть, если в инфраструктуре реализована перегрузка 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
) и избыточную ссылку к входной строке (internal _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
также сможет избежать этого в этих случаях.
Фактически, если вы снова посмотрите на 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
Повеселись! (Но оптимизируйте с умеренностью.)
Ответ 10
Эта проблема также может быть решена с использованием справочной таблицы. Это потребует небольшого количества статической памяти как для кодировщика, так и для декодера. Однако этот метод будет быстрым:
- Таблица кодировщика 512 байтов или 1024 байта (в два раза
размер, если и верхний, и нижний регистр
необходимо)
- Таблица декодера 256 байты или
64 KiB (либо один просмотр char
или двойной поиск char)
Мое решение использует 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). Методы де-кодирования потоков или массивов должны быть реализованы, это всего лишь доказательство концепции.
Ответ 11
Дополнение для ответа с помощью @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:;<=>[email protected]
).
Ответ 12
Это отличный пост. Мне нравится решение Waleed. Я не запускал его с помощью теста patridge, но, похоже, он довольно быстро. Мне также нужен обратный процесс, преобразовывая шестнадцатеричную строку в массив байтов, поэтому я написал ее как обращение к решению Waleed. Не уверен, что это быстрее, чем оригинальное решение Tomalak. Опять же, я не запускал обратный процесс через тест patridge.
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;
}
Ответ 13
Почему это сложно? Это просто в Visual Studio 2008:
С#:
string hex = BitConverter.ToString(YourByteArray).Replace("-", "");
VB:
Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Ответ 14
Не собирать ответы на многие ответы здесь, но я нашел довольно оптимальную (~ 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 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
и перемещал (явно избыточную) последовательность символов к nibble if
другому методу, но это было самым быстрым из них.
(Я признаю, что это отвечает на половину вопроса. Я чувствовал, что преобразование string- > byte [] было недопредставлено, а байт [] → строковый угол кажется хорошо охваченным. Таким образом, этот ответ.)
Ответ 15
Безопасные версии:
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;
}
}
}
BTW
Для тестового тестирования инициализация алфавита каждый раз, когда функция преобразования называется неправильной, алфавит должен быть const (для строки) или статическим readonly (для char []). Затем преобразование байт [] в алфавитном порядке в строку становится так же быстро, как версии манипулирования байтами.
И, конечно, тест должен быть скомпилирован в Release (с оптимизацией), а опция debug "Suppress JIT optimization" отключена (такая же для "Включить только мой код", если код должен быть отлаживаемым).
Ответ 16
Обратная функция для кода Waleed Eissa (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);
}
Ответ 17
Методы расширения (отказ от ответственности: полностью непроверенный код, 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();
}
}
и т.д. Используйте любой из трех решений Tomalak (последний из них является методом расширения в строке).
Ответ 18
От разработчиков 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 для себя.
Ответ 19
Я не получил код, который ты предложил работать, Олипро. 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;
}
Ответ 20
С точки зрения скорости это кажется лучше всего здесь:
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);
}
Ответ 21
Для производительности я бы пошел с раствором drphrozens. Крошечной оптимизацией для декодера может быть использование таблицы для char, чтобы избавиться от "< 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]));
}
Это просто с головы до головы и не тестировалось или сравнивалось.
Ответ 22
Эта версия 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);
}
Ответ 23
Я войду в этот бит-фрид-турнир, так как у меня есть ответ, который также использует бит-скриптинг для декодирования шестнадцатеричных. Обратите внимание, что использование массивов символов может быть еще быстрее, поскольку вызов методов 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-кода.
Ответ 24
И для вставки в строку SQL (если вы не используете параметры команды):
public static String ByteArrayToSQLHexString(byte[] Source)
{
return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
Ответ 25
Еще одно изменение для разнообразия:
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;
}
Ответ 26
Два mashups, которые складывают две операции полубайта в один.
Вероятно, довольно эффективная версия:
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;
}
Ответ 27
Вот мой выстрел в него. Я создал пару классов расширения для расширения строки и байта. В большом пробном файле производительность сопоставима с Byte Manipulation 2.
Код ниже для ToHexString - это оптимизированная реализация алгоритма поиска и сдвига. Он почти идентичен файлу Behrooz, но он получается с помощью foreach
для итерации, а счетчик быстрее, чем явно индексирование for
.
Он входит в 2-е место позади Byte Manipulation 2 на моей машине и является очень читаемым кодом. Интересны также следующие результаты испытаний:
ToHexStringCharArrayWithCharArrayLookup: 41,589.69 средних тиков (более 1000 прогонов), 1.5X
ToHexStringCharArrayWithStringLookup: 50 764,06 средний тик (более 1000 прогонов), 1.2X
ToHexStringStringBuilderWithCharArrayLookup: 62,812.87 средних тиков (более 1000 прогонов), 1.0X
Основываясь на приведенных выше результатах, кажется безопасным заключить, что:
- Штрафы за индексацию в строку для выполнения поиска по сравнению с
Массив char значителен для большого теста файлов.
- Штрафы за использование StringBuilder известной емкости против char
массив известного размера для создания строки еще более значителен.
Здесь код:
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
Cores: 4 <br/>
Current Clock Speed: 1576 <br/>
Max Clock Speed: 3092 <br/>
Преобразование массива байтов в шестнадцатеричное представление строки
ByteArrayToHexViaByteManipulation2: 39 366,64 средних тика (более 1000 прогонов), 22,4X
ByteArrayToHexViaOptimizedLookupAndShift: 41 588,64 средних тика (более 1000 прогонов), 21,2X
ByteArrayToHexViaLookup: 55 509,56 средних тиков (более 1000 прогонов), 15,9X
ByteArrayToHexViaByteManipulation: 65,349.12 средних тиков (более 1000 прогонов), 13,5X
ByteArrayToHexViaLookupAndShift: 86,926.87 средних тиков (более 1000 работает), 10.2X
ByteArrayToHexStringViaBitConverter: 139,353.73 в среднем тики (более 1000 прогонов), 6.3X
ByteArrayToHexViaSoapHexBinary: 314 598,77 средних тиков (более 1000 прогонов), 2,8X
ByteArrayToHexStringViaStringBuilderForEachByteToString: 344,264.63 средний тик (более 1000 прогонов), 2,6X
ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382 623,44 средний тик (более 1000 прогонов), 2.3X
ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818,111.95 средний тик (более 1000 прогонов), 1.1X
ByteArrayToHexStringViaStringConcatArrayConvertAll: 839,244.84 в среднем тики (более 1000 прогонов), 1.1X
ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867,303.98 средний тик (более 1000 прогонов), 1.0X
ByteArrayToHexStringViaStringJoinArrayConvertAll: 882 710,28 в среднем тики (более 1000 прогонов), 1.0X
Ответ 28
Не оптимизирован для скорости, но больше 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
Ответ 29
Другая быстрая функция...
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;
}
Ответ 30
Другим способом является использование stackalloc
для уменьшения давления памяти ГХ:
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);
}