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

1215

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

  • 5
    Принятый ниже ответ, по-видимому, выделяет ужасное количество строк в преобразовании строки в байты. Мне интересно, как это влияет на производительность
  • 7
    Класс SoapHexBinary делает именно то, что вы хотите, я думаю.
Теги:
arrays
hex

39 ответов

1126
Лучший ответ

Или:

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.

  • 0
    отсутствует точка с запятой в шестнадцатеричной строке.AppendFormat (строка "{0: x2}", b)
  • 17
    Вы используете SubString. Разве этот цикл не выделяет ужасное количество строковых объектов?
Показать ещё 17 комментариев
399

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

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

Я запускал каждый из различных методов преобразования с помощью некоторого грубого тестирования производительности Stopwatch, пробега со случайным предложением (n = 61, 1000 итераций) и пробега с текстом Project Gutenburg (n = 1,238,957, 150 итераций). Вот результаты, от самого быстрого до самого медленного. Все измерения находятся в тиках (10 000 тиков = 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)

Добавлен 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 вариант для ответа на репо. Если вы хотите играть в небезопасной игре, вы можете получить огромный прирост производительности по сравнению с предыдущими победителями как коротких строк, так и больших текстов.

  • 0
    Не могли бы вы проверить код из ответа Валида? Вроде бы очень быстро. stackoverflow.com/questions/311165/...
  • 4
    Несмотря на то, что я сделал код доступным для вас, чтобы сделать то, что вы сами запросили, я обновил код тестирования, включив в него ответ Waleed. Не говоря о раздражительности, это намного быстрее.
Показать ещё 17 комментариев
223

Там класс называется 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();
}
  • 31
    SoapHexBinary доступен в .NET 1.0 и находится в mscorlib. Несмотря на смешное пространство имен, он выполняет именно то, что задал вопрос.
  • 2
    Отличная находка! Обратите внимание, что вам нужно дополнить нечетные строки начальным 0 для GetStringToBytes, как и в другом решении.
Показать ещё 4 комментария
135

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

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

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 позволяет ненужные преобразования из и в байты.
  • 7
    И hex string в byte[] array ?
  • 13
    +1 за правильное цитирование вашего источника после вызова этого чёрного волшебства. Приветствую Ктулху.
Показать ещё 11 комментариев
89

Если вы хотите больше гибкости, чем 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")));

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

  • 20
    Еще короче: String.Concat (Array.ConvertAll (bytes, x => x.ToString ("X2"))
  • 0
    Просто обратите внимание, что хорошая техника maxc нуждается в .net 4.0
Показать ещё 4 комментария
57

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

  • 10
    Только отвечает на половину вопроса.
  • 2
    Где вторая часть ответа?
54

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

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;
}
  • 0
    Почему при создании таблицы поиска в небезопасной версии меняются кусочки предварительно вычисленного байта? Я думал, что порядок байтов только изменил порядок объектов, которые были сформированы из нескольких байтов.
  • 0
    @RaifAtef Здесь важен не порядок грызунов. Но порядка 16 битных слов в 32-битном целом числе. Но я подумываю переписать его, чтобы один и тот же код мог выполняться независимо от порядка байтов.
Показать ещё 6 комментариев
50

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

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).

  • 0
    не говоря уже о том, что для этого используется меньше всего памяти. Никаких промежуточных строк не создано.
  • 7
    Только отвечает на половину вопроса.
Показать ещё 5 комментариев
15

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

  • Таблица кодировщика 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). Методы де-кодирования потоков или массивов должны быть реализованы, это всего лишь доказательство концепции.

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

Это ответ на пересмотренный вариант 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

Повеселись! (Но оптимизируйте с умеренностью.)

12

Дополнение для ответа с помощью @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...).

9

Это отличный пост. Мне нравится решение 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;
}
  • 0
    В этом коде предполагается, что шестнадцатеричная строка использует буквенные символы верхнего регистра и взрывается, если шестнадцатеричная строка использует буквенные символы верхнего регистра Возможно, вы захотите сделать преобразование в верхнем регистре для входной строки, чтобы быть в безопасности.
  • 0
    Это проницательное наблюдение, Марк. Код был написан, чтобы полностью изменить решение Валида. Вызов ToUpper несколько замедлит алгоритм, но позволит обрабатывать строчные буквенные символы.
Показать ещё 2 комментария
8

Почему это сложно? Это просто в Visual Studio 2008:

С#:

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 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 [] было недопредставлено, а байт [] → строковый угол кажется хорошо охваченным. Таким образом, этот ответ.)

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

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

И, конечно, тест должен быть скомпилирован в Release (с оптимизацией), а опция debug "Suppress JIT optimization" отключена (такая же для "Включить только мой код", если код должен быть отлаживаемым).

4

Обратная функция для кода 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);
    }
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();
}

В то время как вышеописанный чист, компактные, разработчики производительности будут кричать об этом с помощью счетчиков. Вы можете получить максимальную производительность с улучшенной версией оригинального ответа 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 для себя.

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

Методы расширения (отказ от ответственности: полностью непроверенный код, 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 (последний из них является методом расширения в строке).

  • 0
    Вам, вероятно, следует протестировать код, прежде чем предлагать его для такого вопроса.
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-кода.

  • 0
    Хм, я действительно должен оптимизировать это для Char[] и использовать Char внутри, а не в int ...
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);
    }
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;
}
1

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

Вот мой выстрел в него. Я создал пару классов расширения для расширения строки и байта. В большом пробном файле производительность сопоставима с 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


1

Два 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;
}
  • 1
    HexStringToByteArray ("09") возвращает 0x02, что плохо
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

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

Это просто с головы до головы и не тестировалось или сравнивалось.

1

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

public static String ByteArrayToSQLHexString(byte[] Source)
{
    return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
0

Основное решение с поддержкой расширения

public static class Utils
{
    public static byte[] ToBin(this string hex)
    {
        int NumberChars = hex.Length;
        byte[] bytes = new byte[NumberChars / 2];
        for (int i = 0; i < NumberChars; i += 2)
            bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
        return bytes;
    }
    public static string ToHex(this byte[] ba)
    {
        return  BitConverter.ToString(ba).Replace("-", "");
    }
}

И используйте этот класс, как показано ниже.

    byte[] arr1 = new byte[] { 1, 2, 3 };
    string hex1 = arr1.ToHex();
    byte[] arr2 = hex1.ToBin();
0
static string ByteArrayToHexViaLookupPerByte2(byte[] bytes)
{                
        var result3 = new uint[bytes.Length];
        for (int i = 0; i < bytes.Length; i++)
                result3[i] = _Lookup32[bytes[i]];
        var handle = GCHandle.Alloc(result3, GCHandleType.Pinned);
        try
        {
                var result = Marshal.PtrToStringUni(handle.AddrOfPinnedObject(), bytes.Length * 2);
                return result;
        }
        finally
        {
                handle.Free();
        }
}

Эти функции в моих тестах всегда являются второй записью после небезопасной реализации.

К сожалению, тестовый стенд не так надежен... если вы запустите его несколько раз, список перетасовывается настолько, что кто знает после небезопасного, который действительно самый быстрый! Он не учитывает предварительное прогревание, время компиляции jit и результаты производительности GC. Я хотел бы переписать его, чтобы получить больше информации, но у меня не было на это времени.

0

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

    /// <summary>
    /// Converts the byte array to a hex string very fast. Excellent job
    /// with code lightly adapted from 'community wiki' here: /questions/10949/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa/73922#73922
    /// (the function was originally named: ByteToHexBitFiddle). Now allows a native lowerCase option
    /// to be input and allows null or empty inputs (null returns null, empty returns empty).
    /// </summary>
    public static string ToHexString(this byte[] bytes, bool lowerCase = false)
    {
        if (bytes == null)
            return null;
        else if (bytes.Length == 0)
            return "";

        char[] c = new char[bytes.Length * 2];

        int b;
        int xAddToAlpha = lowerCase ? 87 : 55;
        int xAddToDigit = lowerCase ? -39 : -7;

        for (int i = 0; i < bytes.Length; i++) {

            b = bytes[i] >> 4;
            c[i * 2] = (char)(xAddToAlpha + b + (((b - 10) >> 31) & xAddToDigit));

            b = bytes[i] & 0xF;
            c[i * 2 + 1] = (char)(xAddToAlpha + b + (((b - 10) >> 31) & xAddToDigit));
        }

        string val = new string(c);
        return val;
    }

    public static string ToHexString(this IEnumerable<byte> bytes, bool lowerCase = false)
    {
        if (bytes == null)
            return null;
        byte[] arr = bytes.ToArray();
        return arr.ToHexString(lowerCase);
    }
0

Существует также XmlWriter.WriteBinHex (см. Страницу MSDN). Это очень полезно, если вам нужно поместить шестнадцатеричную строку в поток XML.

Вот отдельный метод, чтобы увидеть, как он работает:

    public static string ToBinHex(byte[] bytes)
    {
        XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
        xmlWriterSettings.ConformanceLevel = ConformanceLevel.Fragment;
        xmlWriterSettings.CheckCharacters = false;
        xmlWriterSettings.Encoding = ASCIIEncoding.ASCII;
        MemoryStream memoryStream = new MemoryStream();
        using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, xmlWriterSettings))
        {
            xmlWriter.WriteBinHex(bytes, 0, bytes.Length);
        }
        return Encoding.ASCII.GetString(memoryStream.ToArray());
    }
0

Я думаю, его скорость стоит 16 дополнительных байтов.

    static char[] hexes = new char[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    public static string ToHexadecimal (this byte[] Bytes)
    {
        char[] Result = new char[Bytes.Length << 1];
        int Offset = 0;
        for (int i = 0; i != Bytes.Length; i++) {
            Result[Offset++] = hexes[Bytes[i] >> 4];
            Result[Offset++] = hexes[Bytes[i] & 0x0F];
        }
        return new string(Result);
    }
  • 2
    Это на самом деле медленнее, чем другие подходы, основанные на поиске таблиц (по крайней мере, в моих тестах). Использование != Вместо < нарушает некоторые шаблоны оптимизации JIT, и дополнительный счетчик для Offset кажется дорогостоящим.
0

Это работает, чтобы перейти от строки к массиву байтов...

public static byte[] StrToByteArray(string str)
    {
        Dictionary<string, byte> hexindex = new Dictionary<string, byte>();
        for (byte i = 0; i < 255; i++)
            hexindex.Add(i.ToString("X2"), i);

        List<byte> hexres = new List<byte>();
        for (int i = 0; i < str.Length; i += 2)
            hexres.Add(hexindex[str.Substring(i, 2)]);

        return hexres.ToArray();
    }
0

Если производительность имеет значение, здесь оптимизированное решение:

    static readonly char[] _hexDigits = "0123456789abcdef".ToCharArray();
    public static string ToHexString(this byte[] bytes)
    {
        char[] digits = new char[bytes.Length * 2];
        for (int i = 0; i < bytes.Length; i++)
        {
            int d1, d2;
            d1 = Math.DivRem(bytes[i], 16, out d2);
            digits[2 * i] = _hexDigits[d1];
            digits[2 * i + 1] = _hexDigits[d2];
        }
        return new string(digits);
    }

Это примерно в 2,5 раза быстрее, чем BitConverter.ToString, и примерно в 7 раз быстрее, чем BitConverter.ToString + удаление символов "-".

  • 4
    Если производительность имеет значение, вы бы не использовали Math.DivRem для разделения байта на два куска.
  • 0
    @ dolmen, ты тестировал производительность с Math.DivRem и без Math.DivRem ? Я серьезно сомневаюсь, что это как- то влияет на производительность: реализация Math.DivRem - это именно то, что вы бы делали вручную, а метод очень прост, поэтому он всегда встроен в JIT (на самом деле он предназначен для встраивания, как предложено в TargetedPatchingOptOut Атрибут применяется к нему)
Показать ещё 1 комментарий
0

Если вы хотите получить "увеличение скорости 4 раза", сообщенное wcoenen, тогда, если это не очевидно: замените hex.Substring(i, 2) на hex[i]+hex[i+1]

Вы также можете сделать еще один шаг и избавиться от i+=2, используя i++ в обоих местах.

-4

Я подозреваю, что скорость этого будет сбивать носки с большинства других тестов...

Public Function BufToHex(ByVal buf() As Byte) As String
    Dim sB As New System.Text.StringBuilder
    For i As Integer = 0 To buf.Length - 1
        sB.Append(buf(i).ToString("x2"))
    Next i
    Return sB.ToString
End Function
  • 2
    Что заставляет вас думать, что? Вы создаете новый строковый объект для каждого байта в буфере, и вы не предварительно определяете размер строителя строк (что может привести к многократному изменению размера буфера в больших массивах).
  • 0
    Обычное английское преобразование байтов :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню