Как преобразовать байтовый массив в шестнадцатеричную строку и наоборот?

1373

Как можно преобразовать байтовый массив в шестнадцатеричную строку и наоборот?

alextansc
источник
8
Принятый ответ, приведенный ниже, выделяет ужасное количество строк в преобразовании строки в байты. Мне интересно, как это влияет на производительность
Вим Коенен
9
Класс SoapHexBinary делает именно то, что вы хотите, я думаю.
Майкрофт
Мне кажется, что задавать 2 вопроса в 1 посте не совсем стандартно.
SandRock

Ответы:

1353

Или:

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.

Томалак
источник
24
Вы используете SubString. Разве этот цикл не выделяет ужасное количество строковых объектов?
Вим Коенен
30
Честно говоря, до тех пор, пока производительность не резко упадет, я склонен игнорировать это и доверять среде исполнения и GC позаботиться об этом.
Томалак
87
Поскольку байт - это два полубайта, любая шестнадцатеричная строка, которая корректно представляет массив байтов, должна иметь четное количество символов. 0 нигде не должен быть добавлен - для добавления было бы сделано предположение о недопустимых данных, которые потенциально опасны. Во всяком случае, метод StringToByteArray должен генерировать исключение FormatException, если шестнадцатеричная строка содержит нечетное количество символов.
Дэвид Бойк
7
@ 00jt Вы должны сделать предположение, что F == 0F. Либо он совпадает с 0F, либо вход был обрезан, и F фактически является началом чего-то, что вы не получили. Это зависит от вашего контекста, чтобы сделать эти предположения, но я считаю, что функция общего назначения должна отклонять нечетные символы как недействительные, вместо того, чтобы делать это предположение для вызывающего кода.
Дэвид Бойк
11
@DavidBoike Вопрос не имел никакого отношения к тому, «как обрабатывать возможные отсеченные значения потока». Это говорит о String. String myValue = 10.ToString ("X"); myValue это "A", а не "0A". Теперь прочитайте эту строку обратно в байты, ой, вы сломали ее.
00
488

Анализ производительности

Примечание: новый лидер с 2015-08-20.

Я проверил каждый из различных методов преобразования через некоторое грубое Stopwatchтестирование производительности, прогон со случайным предложением (n = 61, 1000 итераций) и прогон с текстом Project Gutenburg (n = 1 238 957, 150 итераций). Вот результаты, примерно от самого быстрого до самого медленного. Все измерения даны в тиках ( 10000 тиков = 1 мс ), и все относительные ноты сравниваются с [самой медленной] StringBuilderреализацией. Используемый код см. Ниже или в репозитории тестовой инфраструктуры, где я сейчас поддерживаю код для запуска этого.

отказ

ВНИМАНИЕ: не полагайтесь на эти характеристики для чего-то конкретного; это просто пример пробных данных. Если вам действительно нужна первоклассная производительность, протестируйте эти методы в среде, представляющей ваши производственные потребности, с данными, указывающими, что вы будете использовать.

Результаты

Таблицы поиска взяли на себя инициативу по манипулированию байтами. По сути, существует некоторая форма предварительного вычисления того, какой любой данный клев или байт будет в шестнадцатеричном виде. Затем, просматривая данные, вы просто просматриваете следующую часть, чтобы увидеть, какой это будет шестнадцатеричная строка. Это значение затем добавляется к полученному выводу строки некоторым способом. Долгое время манипулирование байтами, потенциально трудное для чтения некоторыми разработчиками, было наиболее эффективным подходом.

Ваша лучшая ставка по-прежнему будет найти некоторые репрезентативные данные и опробовать их в производственной среде. Если у вас разные ограничения памяти, вы можете предпочесть метод с меньшим количеством выделений, чем метод, который был бы быстрее, но потреблял бы больше памяти.

Код тестирования

Не стесняйтесь играть с кодом тестирования, который я использовал. Версия включена здесь, но не стесняйтесь клонировать репо и добавлять свои собственные методы. Пожалуйста, отправьте запрос на удаление, если вы найдете что-нибудь интересное или хотите помочь улучшить используемую им инфраструктуру тестирования.

  1. Добавьте новый статический метод ( Func<byte[], string>) в /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Добавьте имя этого метода к TestCandidatesвозвращаемому значению в том же классе.
  3. Убедитесь, что вы используете нужную вам версию ввода, предложение или текст, переключая комментарии GenerateTestInputв том же классе.
  4. Нажмите 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)

Добавлен ответ Валида на анализ. Довольно быстро.

Обновление (2011-10-05)

Добавлен string.Concat Array.ConvertAllвариант для полноты (требуется .NET 4.0). Наравне с string.Joinверсией.

Обновление (2012-02-05)

Тест репо включает в себя больше вариантов, таких как StringBuilder.Append(b.ToString("X2")). Никто не расстроил результаты какие-либо. foreachбыстрее, чем {IEnumerable}.Aggregate, например, но BitConverterвсе же выигрывает.

Обновление (2012-04-03)

В SoapHexBinaryанализ добавлен ответ Майкрофта , который занял третье место.

Обновление (2013-01-15)

Добавлен ответ CodesInChaos по манипулированию байтами, который занял первое место (с большим запасом для больших блоков текста).

Обновление (2013-05-23)

Добавлен ответ поиска Натана Моинвазири и вариант из блога Брайана Ламберта. Оба довольно быстрые, но не идут впереди на тестовой машине, которую я использовал (AMD Phenom 9750).

Обновление (2014-07-31)

Добавлен новый байтовый ответ @ CodesInChaos. Похоже, что он взял на себя инициативу как по тестам предложений, так и по полнотекстовым тестам.

Обновление (2015-08-20)

Добавлены оптимизации и варианты airbreatherunsafe к репо этого ответа . Если вы хотите играть в небезопасную игру, вы можете получить огромный выигрыш в производительности по сравнению с любым из предыдущих лучших победителей как в коротких строках, так и в больших текстах.

Patridge
источник
Не могли бы вы проверить код из ответа Валида? Вроде бы очень быстро. stackoverflow.com/questions/311165/…
Кристиан Диаконеску
5
Несмотря на то, что я сделал код доступным для вас, чтобы выполнить то, что вы сами запросили, я обновил код тестирования, включив в него ответ Waleed. Не говоря о раздражительности, это гораздо быстрее.
Патридж
2
@CodesInChaos Готово. И в моих тестах он тоже немного выиграл. Я не претендую на то, чтобы полностью понять ни один из лучших методов, но они легко скрыты от прямого взаимодействия.
Патридж
6
Этот ответ не намерен отвечать на вопрос о том, что является «естественным» или обычным явлением. Цель состоит в том, чтобы дать людям некоторые базовые показатели производительности, поскольку, когда вам нужно выполнить это преобразование, вы склонны делать их много. Если кому-то нужна грубая скорость, он просто запускает эталонные тесты с соответствующими тестовыми данными в нужной вычислительной среде. Затем уберите этот метод в метод расширения, где вы никогда больше не посмотрите на его реализацию (например, bytes.ToHexStringAtLudicrousSpeed()).
Патридж
2
Только что произвел реализацию на основе высокоэффективной таблицы поиска. Его безопасный вариант примерно на 30% быстрее, чем текущий лидер на моем процессоре. Небезопасные варианты еще быстрее. stackoverflow.com/a/24343727/445517
CodesInChaos
244

Есть класс с именем 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();
}
Mykroft
источник
35
SoapHexBinary доступен в .NET 1.0 и находится в mscorlib. Несмотря на смешное пространство имен, он выполняет именно то, что задал вопрос.
Хитрый Грифон
4
Отличная находка! Обратите внимание, что вам нужно дополнить нечетные строки начальным 0 для GetStringToBytes, как и в другом решении.
Картер Медлин
Вы видели реализацию мысли? Принятый ответ имеет лучшее ИМХО.
Мфлорян
6
Интересно увидеть реализацию Mono здесь: github.com/mono/mono/blob/master/mcs/class/corlib/…
Джереми,
1
SoapHexBinary не поддерживается в .NET Ядра / .NET Standard ...
ЮФО
141

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

Это также довольно быстро.

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


Оставь всякую надежду, вы, кто входит сюда

Объяснение странной мелочи:

  1. bytes[i] >> 4извлекает верхний кусок байта
    bytes[i] & 0xFизвлекает нижний кусок байта
  2. b - 10
    это < 0для значений b < 10, которые станут десятичной цифрой
    является >= 0для значений b > 10, которые станут письмом от Aдо F.
  3. Использование i >> 3132-разрядного целого числа со знаком извлекает знак благодаря расширению знака. Это будет -1для i < 0и 0для i >= 0.
  4. Объединяя 2) и 3), показывает, что (b-10)>>31будет 0для букв и -1цифр.
  5. Если посмотреть на регистр букв, последнее слагаемое становится 0и bнаходится в диапазоне от 10 до 15. Мы хотим сопоставить его с A(65) - F(70), что подразумевает добавление 55 ( 'A'-10).
  6. Рассматривая случай цифр, мы хотим адаптировать последнее слагаемое, чтобы оно отображалось 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], поэтому я выбрал этот вариант.
  • Создание bint позволяет ненужные преобразования от и до байта.
CodesInChaos
источник
10
А hex stringк byte[] array?
AaA
15
+1 за правильное цитирование вашего источника после вызова этого чёрного волшебства. Приветствую Ктулху.
Эдвард
4
Как насчет строки в байт []?
Шайфул Низам Яхья
9
Ницца! Для тех, кому нужен вывод в нижнем регистре, выражение явно меняется на87 + b + (((b-10)>>31)&-39)
eXavier
2
@AaA Вы сказали " byte[] array", что буквально означает массив байтовых массивов, или byte[][]. Я просто подшучивал.
CoolOppo
97

Если вам нужна большая гибкость, чем 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")));

(Последнее из комментария к оригинальному сообщению.)

будет деканом
источник
21
Еще короче: String.Concat (Array.ConvertAll (bytes, x => x.ToString ("X2"))
Нестор
14
Еще короче: String.Concat (bytes.Select (b => b.ToString ("X2"))) [.NET4]
Аллон Гуралнек
14
Только отвечает на половину вопроса.
Хитрый Грифон
1
Зачем второму нужен .Net 4? String.Concat находится в .Net 2.0.
Polyfun
2
эти петли "стиля 90-х", как правило, быстрее, но на ничтожно малое количество, что не имеет значения в большинстве случаев. Тем не менее, стоит упомянуть
Остин_Андерсон
69

Еще один подход на основе таблицы поиска. Этот использует только одну таблицу поиска для каждого байта вместо таблицы поиска для каждого куска.

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;
}
оборота CodesInChaos
источник
Почему при создании таблицы поиска в небезопасной версии меняются кусочки предварительно вычисленного байта? Я думал, что порядок байтов только изменил порядок объектов, которые были сформированы из нескольких байтов.
Раиф Атеф
@RaifAtef Здесь важен не порядок грызунов. Но порядка 16 битных слов в 32-битном целом числе. Но я подумываю переписать его, чтобы один и тот же код мог работать независимо от порядка байтов.
CodesInChaos
Перечитывая код, я думаю, что вы сделали это, потому что, когда вы приводите char * позже к uint * и назначаете его (при генерации шестнадцатеричного char), среда выполнения / CPU переворачивают байты (поскольку uint не обрабатывается так же, как 2 отдельных 16-битных символа), так что вы предварительно переворачиваете их для компенсации. Я прав ? Endianness сбивает с толку :-).
Раиф Атеф
4
Это просто ответ на половину вопроса ... Как насчет шестнадцатеричной строки в байтах?
Narvalex
3
@ CodesInChaos Интересно, Spanможно ли использовать сейчас вместо unsafe??
Конрад
64

Вы можете использовать метод 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 [])

Baget
источник
14
Только отвечает на половину вопроса.
Хитрый Грифон
3
Где вторая часть ответа?
Саван
56

Я только что столкнулся с той же проблемой сегодня, и я наткнулся на этот код:

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 () (самый быстрый согласно сообщению Патриджа).

Waleed Eissa
источник
не говоря уже о том, что для этого используется меньше всего памяти. Никаких промежуточных строк не создано.
Chochos
8
Только отвечает на половину вопроса.
Хитрый Грифон
Это здорово, потому что работает практически на любой версии NET, включая NETMF. Победитель!
Jonesome Восстановить Монику
1
Принятый ответ предоставляет 2 превосходных метода HexToByteArray, которые представляют другую половину вопроса. Решение Waleed отвечает на вопрос о том, как это сделать, не создавая огромное количество строк в процессе.
Брендтен Эйкштадт
Копирует и перераспределяет ли новая строка (c) или она достаточно умна, чтобы знать, когда она может просто обернуть символ []?
Jjxtra
19

Это ответ на пересмотр 4 из весьма популярного ответа Томалак в (и последующих правок).

Я сделаю так, что это редактирование неверно, и объясню, почему оно может быть отменено. Попутно вы можете узнать кое-что о некоторых внутренних элементах и ​​увидеть еще один пример того, что такое преждевременная оптимизация и как она может вас укусить.

tl; dr: Просто используйте, Convert.ToByteи String.Substringесли вы спешите («Оригинальный код» ниже), то это лучшая комбинация, если вы не хотите повторно внедрять Convert.ToByte. Используйте что-то более продвинутое (см. Другие ответы), которое не используется, Convert.ToByteесли вам нужна производительность. Есть не что - нибудь еще другое использование , чем String.Substringв сочетании с Convert.ToByte, если кто - то имеет что - то интересное , чтобы сказать об этом в комментариях этого ответа.

предупреждение: Ответ на этот вопрос может устареть , еслиConvert.ToByte(char[], Int32) перегрузка осуществляется в рамках. Это вряд ли произойдет в ближайшее время.

Как правило, я не очень люблю говорить «не оптимизировать преждевременно», потому что никто не знает, когда «преждевременно». Единственное, что вы должны учитывать при принятии решения об оптимизации или нет, это: «У меня есть время и ресурсы для правильного исследования подходов к оптимизации?». Если вы этого не сделаете, то это слишком рано, подождите , пока ваш проект не будет более зрелым или до тех пор , пока нужна производительность (если есть реальная необходимость, то вы будете делать время). А пока сделайте самое простое, что могло бы сработать.

Оригинальный код:

    public static byte[] HexadecimalStringToByteArray_Original(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        for (var i = 0; i < outputLength; i++)
            output[i] = Convert.ToByte(input.Substring(i * 2, 2), 16);
        return output;
    }

Редакция 4:

    public static byte[] HexadecimalStringToByteArray_Rev4(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
                output[i] = Convert.ToByte(new string(new char[2] { (char)sr.Read(), (char)sr.Read() }), 16);
        }
        return output;
    }

Редакция избегает String.Substringи используетStringReader вместо этого. Данная причина:

Изменить: вы можете улучшить производительность для длинных строк с помощью однопроходного парсера, например так:

Ну, глядя на код ссылки дляString.Substring , он уже явно "однопроходный"; а почему не должно быть? Он работает на уровне байтов, а не на суррогатных парах.

Тем не менее, он выделяет новую строку, но тогда вам нужно выделить ее для передачи в Convert.ToByteлюбом случае. Кроме того, решение, представленное в ревизии, выделяет еще один объект на каждой итерации (массив из двух символов); Вы можете безопасно поместить это распределение за пределы цикла и повторно использовать массив, чтобы избежать этого.

    public static byte[] HexadecimalStringToByteArray(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
            {
                numeral[0] = (char)sr.Read();
                numeral[1] = (char)sr.Read();
                output[i] = Convert.ToByte(new string(numeral), 16);
            }
        }
        return output;
    }

Каждый шестнадцатеричный numeralпредставляет один октет с использованием двух цифр (символов).

Но тогда зачем звонить StringReader.Readдважды? Просто вызовите его вторую перегрузку и попросите его прочитать сразу два символа в массиве из двух символов; и уменьшить количество звонков на два.

    public static byte[] HexadecimalStringToByteArray(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
            {
                var read = sr.Read(numeral, 0, 2);
                Debug.Assert(read == 2);
                output[i] = Convert.ToByte(new string(numeral), 16);
            }
        }
        return output;
    }

То, что у вас осталось, это средство чтения строк, единственным добавленным «значением» которого является параллельный индекс (внутренний _pos), который вы могли бы объявить самостоятельно (как, jнапример), избыточная переменная длины (внутренняя _length) и избыточная ссылка на вход строка (внутренняя_s ). Другими словами, это бесполезно.

Если вам интересно, как Read«читается», просто посмотрите на код , все, что он делает, это вызывает String.CopyToвходную строку. Все остальное - это просто расходы на ведение бухгалтерского учета для поддержания ценностей, которые нам не нужны.

Итак, удалите читатель строки и позвоните CopyToсебе; это проще, понятнее и эффективнее.

    public static byte[] HexadecimalStringToByteArray(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        for (int i = 0, j = 0; i < outputLength; i++, j += 2)
        {
            input.CopyTo(j, numeral, 0, 2);
            output[i] = Convert.ToByte(new string(numeral), 16);
        }
        return output;
    }

Вам действительно нужен jиндекс, который увеличивается с шагом в две параллели i? Конечно, нет, просто умножьте iна два (что компилятор должен уметь оптимизировать до сложения).

    public static byte[] HexadecimalStringToByteArray_BestEffort(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        for (int i = 0; i < outputLength; i++)
        {
            input.CopyTo(i * 2, numeral, 0, 2);
            output[i] = Convert.ToByte(new string(numeral), 16);
        }
        return output;
    }

Как выглядит решение сейчас? Точно так же, как это было в начале, только вместо того, String.Substringчтобы использовать для выделения строки и копирования в нее данных, вы используете промежуточный массив, в который вы копируете шестнадцатеричные цифры, затем выделяете строку самостоятельно и снова копируете данные из массив и в строку (когда вы передаете его в конструкторе строки). Вторая копия может быть оптимизирована, если строка уже находится в пуле интернирования, но в этом String.Substringслучае ее также можно будет избежать.

Фактически, если вы посмотрите String.Substringснова, вы увидите, что он использует некоторые низкоуровневые внутренние знания о том, как строятся строки, чтобы распределить строку быстрее, чем вы обычно это делаете, и он вставляет тот же код, который используется CopyToнепосредственно там, чтобы избежать накладные расходы.

String.Substring

  • В худшем случае: одно быстрое размещение, одна быстрая копия.
  • В лучшем случае: нет выделения, нет копии.

Ручной метод

  • В худшем случае: два обычных размещения, одно обычное копирование, одно быстрое копирование.
  • В лучшем случае: одно нормальное распределение, одна нормальная копия.

Вывод? Если вы хотите использоватьConvert.ToByte(String, Int32) (потому что вы не хотите повторно реализовывать эту функциональность самостоятельно), похоже, нет способа победить String.Substring; все, что вы делаете, это бегаете кругами, заново изобретая колесо (только с неоптимальными материалами).

Обратите внимание, что использование Convert.ToByteи String.Substringявляется вполне допустимым выбором, если вам не нужна высокая производительность. Помните: выбирайте альтернативу, только если у вас есть время и ресурсы, чтобы выяснить, как она работает правильно.

Если Convert.ToByte(char[], Int32)бы это было, конечно, все было бы иначе (можно было бы сделать то, что я описал выше, и полностью избежать String).

Я подозреваю, что люди, которые сообщают о лучшей производительности, «избегая String.Substring», также избегают Convert.ToByte(String, Int32), что вы действительно должны делать, если вам все равно нужна производительность. Посмотрите на бесчисленное множество других ответов, чтобы обнаружить все различные подходы для этого.

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

Теперь все это звучит хорошо и логично, надеюсь, даже очевидно, если вам удалось продвинуться так далеко. Но так ли это?

Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz
    Cores: 8
    Current Clock Speed: 2600
    Max Clock Speed: 2600
--------------------
Parsing hexadecimal string into an array of bytes
--------------------
HexadecimalStringToByteArray_Original: 7,777.09 average ticks (over 10000 runs), 1.2X
HexadecimalStringToByteArray_BestEffort: 8,550.82 average ticks (over 10000 runs), 1.1X
HexadecimalStringToByteArray_Rev4: 9,218.03 average ticks (over 10000 runs), 1.0X

Да!

Реквизит в Partridge для каркаса скамейки, его легко взломать. В качестве входных данных используется следующий хэш SHA-1, повторенный 5000 раз, чтобы получить строку длиной 100 000 байт.

209113288F93A9AB8E474EA78D899AFDBB874355

Радоваться, веселиться! (Но оптимизируйте с модерацией.)

tne
источник
ошибка: {"Не удалось найти какие-либо узнаваемые цифры."}
Прия Джагтап
17

Дополнение к ответу @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...).

CoperNick
источник
16

Эту проблему также можно решить с помощью справочной таблицы. Это потребует небольшого количества статической памяти как для кодера, так и для декодера. Однако этот метод будет быстрым:

  • Таблица кодировщика 512 байт или 1024 байт (в два раза больше, если требуется прописная и строчная буквы)
  • Таблица декодера 256 байтов или 64 КиБ (либо поиск одного символа, либо поиск двух символов)

Мое решение использует 1024 байта для таблицы кодирования и 256 байтов для декодирования.

Декодирование

private static readonly byte[] LookupTable = new byte[] {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

private static byte Lookup(char c)
{
  var b = LookupTable[c];
  if (b == 255)
    throw new IOException("Expected a hex character, got " + c);
  return b;
}

public static byte ToByte(char[] chars, int offset)
{
  return (byte)(Lookup(chars[offset]) << 4 | Lookup(chars[offset + 1]));
}

кодирование

private static readonly char[][] LookupTableUpper;
private static readonly char[][] LookupTableLower;

static Hex()
{
  LookupTableLower = new char[256][];
  LookupTableUpper = new char[256][];
  for (var i = 0; i < 256; i++)
  {
    LookupTableLower[i] = i.ToString("x2").ToCharArray();
    LookupTableUpper[i] = i.ToString("X2").ToCharArray();
  }
}

public static char[] ToCharLower(byte[] b, int bOffset)
{
  return LookupTableLower[b[bOffset]];
}

public static char[] ToCharUpper(byte[] b, int bOffset)
{
  return LookupTableUpper[b[bOffset]];
}

сравнение

StringBuilderToStringFromBytes:   106148
BitConverterToStringFromBytes:     15783
ArrayConvertAllToStringFromBytes:  54290
ByteManipulationToCharArray:        8444
TableBasedToCharArray:              5651 *

* это решение

Запись

Во время декодирования могут возникнуть IOException и IndexOutOfRangeException (если символ имеет слишком высокое значение> 256). Методы для де / кодирования потоков или массивов должны быть реализованы, это просто подтверждение концепции.

drphrozen
источник
2
Использование памяти в 256 байт незначительно при запуске кода в CLR.
дольмен
9

Это отличная статья. Мне нравится решение Валида. Я не прошел тест Патриджа, но, похоже, он проходит довольно быстро. Мне также потребовался обратный процесс, преобразование шестнадцатеричной строки в байтовый массив, поэтому я написал ее как обращение решения Валида. Не уверен, что это быстрее, чем оригинальное решение Томалака. Опять же, я не запускал обратный процесс через тест Патриджа.

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 несколько замедлит алгоритм, но позволит обрабатывать строчные буквенные символы.
Крис Ф
3
Convert.ToByte (topChar + bottomChar) можно записать как (байт) (topChar + bottomChar)
Амир Резаи
Чтобы обрабатывать оба случая без большого штрафа за производительность,hexString[i] &= ~0x20;
Бен Фойгт
9

Зачем делать это сложным? Это просто в Visual Studio 2008:

C #:

string hex = BitConverter.ToString(YourByteArray).Replace("-", "");

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Крейг Полтон
источник
2
причина в производительности, когда вам нужно высокопроизводительное решение. :)
Рикки
7

Чтобы не отвечать на многие ответы здесь, я нашел довольно оптимальную (~ 4,5 раза лучше, чем принято) прямую реализацию синтаксического анализатора шестнадцатеричных строк. Сначала вывод моих тестов (первая партия - моя реализация):

Give me that string:
04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939f

Time to parse 100,000 times: 50.4192 ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F

Accepted answer: (StringToByteArray)
Time to parse 100000 times: 233.1264ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F

With Mono's implementation:
Time to parse 100000 times: 777.2544ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F

With SoapHexBinary:
Time to parse 100000 times: 845.1456ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F

Строки base64 и BitConverter'd проверяют правильность. Обратите внимание, что они равны.

Реализация:

public static byte[] ToByteArrayFromHex(string hexString)
{
  if (hexString.Length % 2 != 0) throw new ArgumentException("String must have an even length");
  var array = new byte[hexString.Length / 2];
  for (int i = 0; i < hexString.Length; i += 2)
  {
    array[i/2] = ByteFromTwoChars(hexString[i], hexString[i + 1]);
  }
  return array;
}

private static byte ByteFromTwoChars(char p, char p_2)
{
  byte ret;
  if (p <= '9' && p >= '0')
  {
    ret = (byte) ((p - '0') << 4);
  }
  else if (p <= 'f' && p >= 'a')
  {
    ret = (byte) ((p - 'a' + 10) << 4);
  }
  else if (p <= 'F' && p >= 'A')
  {
    ret = (byte) ((p - 'A' + 10) << 4);
  } else throw new ArgumentException("Char is not a hex digit: " + p,"p");

  if (p_2 <= '9' && p_2 >= '0')
  {
    ret |= (byte) ((p_2 - '0'));
  }
  else if (p_2 <= 'f' && p_2 >= 'a')
  {
    ret |= (byte) ((p_2 - 'a' + 10));
  }
  else if (p_2 <= 'F' && p_2 >= 'A')
  {
    ret |= (byte) ((p_2 - 'A' + 10));
  } else throw new ArgumentException("Char is not a hex digit: " + p_2, "p_2");

  return ret;
}

Я попробовал кое-что с unsafeи переместил (явно избыточную) ifпоследовательность символов в клев на другой метод, но это было быстрее всего.

(Я допускаю, что это отвечает на половину вопроса. Я чувствовал, что преобразование string-> byte [] было недопредставлено, в то время как угол строки byte [] ->, кажется, хорошо покрыт. Таким образом, этот ответ.)

Бен Мошер
источник
1
Для последователей Кнута: я сделал это, потому что мне нужно анализировать несколько тысяч шестнадцатеричных строк каждые несколько минут или около того, поэтому важно, чтобы это было как можно быстрее (как бы во внутреннем цикле). Решение Томалака не заметно медленнее, если много таких разборов не происходит.
Бен Мошер
5

Безопасные версии:

public static class HexHelper
{
    [System.Diagnostics.Contracts.Pure]
    public static string ToHex(this byte[] value)
    {
        if (value == null)
            throw new ArgumentNullException("value");

        const string hexAlphabet = @"0123456789ABCDEF";

        var chars = new char[checked(value.Length * 2)];
        unchecked
        {
            for (int i = 0; i < value.Length; i++)
            {
                chars[i * 2] = hexAlphabet[value[i] >> 4];
                chars[i * 2 + 1] = hexAlphabet[value[i] & 0xF];
            }
        }
        return new string(chars);
    }

    [System.Diagnostics.Contracts.Pure]
    public static byte[] FromHex(this string value)
    {
        if (value == null)
            throw new ArgumentNullException("value");
        if (value.Length % 2 != 0)
            throw new ArgumentException("Hexadecimal value length must be even.", "value");

        unchecked
        {
            byte[] result = new byte[value.Length / 2];
            for (int i = 0; i < result.Length; i++)
            {
                // 0(48) - 9(57) -> 0 - 9
                // A(65) - F(70) -> 10 - 15
                int b = value[i * 2]; // High 4 bits.
                int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
                b = value[i * 2 + 1]; // Low 4 bits.
                val += (b - '0') + ((('9' - b) >> 31) & -7);
                result[i] = checked((byte)val);
            }
            return result;
        }
    }
}

Небезопасные версии Для тех, кто предпочитает производительность и не боится небезопасности. На 35% быстрее ToHex и на 10% быстрее FromHex.

public static class HexUnsafeHelper
{
    [System.Diagnostics.Contracts.Pure]
    public static unsafe string ToHex(this byte[] value)
    {
        if (value == null)
            throw new ArgumentNullException("value");

        const string alphabet = @"0123456789ABCDEF";

        string result = new string(' ', checked(value.Length * 2));
        fixed (char* alphabetPtr = alphabet)
        fixed (char* resultPtr = result)
        {
            char* ptr = resultPtr;
            unchecked
            {
                for (int i = 0; i < value.Length; i++)
                {
                    *ptr++ = *(alphabetPtr + (value[i] >> 4));
                    *ptr++ = *(alphabetPtr + (value[i] & 0xF));
                }
            }
        }
        return result;
    }

    [System.Diagnostics.Contracts.Pure]
    public static unsafe byte[] FromHex(this string value)
    {
        if (value == null)
            throw new ArgumentNullException("value");
        if (value.Length % 2 != 0)
            throw new ArgumentException("Hexadecimal value length must be even.", "value");

        unchecked
        {
            byte[] result = new byte[value.Length / 2];
            fixed (char* valuePtr = value)
            {
                char* valPtr = valuePtr;
                for (int i = 0; i < result.Length; i++)
                {
                    // 0(48) - 9(57) -> 0 - 9
                    // A(65) - F(70) -> 10 - 15
                    int b = *valPtr++; // High 4 bits.
                    int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
                    b = *valPtr++; // Low 4 bits.
                    val += (b - '0') + ((('9' - b) >> 31) & -7);
                    result[i] = checked((byte)val);
                }
            }
            return result;
        }
    }
}

КСТАТИ для тестирования производительности инициализация алфавита каждый раз, когда вызываемая функция преобразования неверна, алфавит должен быть const (для строки) или статическим readonly (для char []). Затем основанное на алфавите преобразование byte [] в строку становится таким же быстрым, как версии манипулирования байтами.

И, конечно, тест должен быть скомпилирован в Release (с оптимизацией) и с отключенной опцией «Отключить оптимизацию JIT» (то же самое для «Enable Just My Code», если код должен быть отлаживаемым).

оборота Марация
источник
5

Обратная функция для кода Валида Эйссы (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);
    }
джеограф
источник
4

Методы расширения (отказ от ответственности: полностью непроверенный код, кстати ...):

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();
    }
}

и т. д. Используйте одно из трех решений Томалака (последнее из которых является методом расширения строки).

Pure.Krome
источник
Вам, вероятно, следует протестировать код, прежде чем предлагать его для такого вопроса.
jww
3

От разработчиков 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 для себя.

отметки
источник
2
Итератор не является главной проблемой этого кода. Вы должны сравнить b.ToSting("X2").
дольмен
2

И для вставки в строку SQL (если вы не используете параметры команды):

public static String ByteArrayToSQLHexString(byte[] Source)
{
    return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
Джек Стро
источник
если Source == nullили Source.Length == 0у нас есть проблемы, сэр!
Андрей Красуцкий
2

С точки зрения скорости это кажется лучше, чем что-либо здесь:

  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);
  }
Алексей Борзенков
источник
2

Я не получил код, который ты предложил работать, Олипро. 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;
}
Фредрик Ху
источник
2

Эта версия 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);
    }
JoseH
источник
2

Я приму участие в этом соревновании по битрейту, так как у меня есть ответ, который также использует битовую скрипку для декодирования шестнадцатеричных чисел. Обратите внимание, что использование символьных массивов может быть еще быстрее, так как вызов 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внутренне вместо целых ...
Maarten Bodewes
Для C # инициализация переменных, где они используются, а не вне цикла, вероятно, предпочтительнее, чтобы позволить компилятору оптимизировать. Я получаю эквивалентную производительность в любом случае.
Петер
2

Для производительности я бы пошел с раствором 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]));
}

Это только с моей головы и не было проверено или сравнительно.

ClausAndersen
источник
1

Еще один вариант разнообразия:

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;          
}
Стас Макутин
источник
1

Не оптимизирован для скорости, но больше 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
MCattle
источник
1

Два коллажа, которые складывают две клевы в одну.

Вероятно, довольно эффективная версия:

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;
}
JJJ
источник
1
HexStringToByteArray ("09") возвращает 0x02, что плохо
CoperNick
1

Другой способ - использовать 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);
}
Кель
источник
1

Вот мой выстрел в это. Я создал пару классов расширения для расширения строки и байта. При тестировании больших файлов производительность сопоставима с Byte Manipulation 2.

Код ниже для ToHexString - оптимизированная реализация алгоритма поиска и сдвига. Он почти идентичен Behrooz, но оказывается, что он использует foreachитерацию, а счетчик работает быстрее, чем явное индексирование.for .

Он занимает второе место после Byte Manipulation 2 на моей машине и является очень читабельным кодом. Следующие результаты испытаний также представляют интерес:

ToHexStringCharArrayWithCharArrayLookup: 41 589,69 средних тиков (более 1000 прогонов), 1,5X ToHexStringCharArrayWithStringLookup: 50 764,06 средних тиков (более 1000 прогонов), 1,2X

Исходя из приведенных выше результатов, можно с уверенностью заключить, что:

  1. Штрафы за индексирование в строку для выполнения поиска по сравнению с массивом символов значительны в тесте большого файла.
  2. Штрафы за использование StringBuilder известной емкости по сравнению с массивом символов известного размера для создания строки еще более значительны.

Вот код:

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


JamieSee
источник
1

Еще одна быстрая функция ...

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;
}
spacepille
источник