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

avatar
alextansc
22 ноября 2008 в 10:03
974778
51
1473

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

Источник
Wim Coenen
6 марта 2009 в 16:41
9

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

Ответы (51)

avatar
Tomalak
27 сентября 2018 в 09:23
1489

Либо:

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.

el2iot2
23 декабря 2008 в 00:37
0

отсутствует точка с запятой в строке hex.AppendFormat ("{0: x2}", b)

Wim Coenen
6 марта 2009 в 16:36
26

Вы используете SubString. Разве этот цикл не выделяет ужасное количество строковых объектов?

Tomalak
6 марта 2009 в 17:11
31

Честно говоря, до тех пор, пока это не резко снизит производительность, я буду игнорировать это и доверять Runtime и GC, которые позаботятся об этом.

Wim Coenen
8 марта 2009 в 18:26
1

FWIW Я мог бы получить 4-кратное ускорение на моей машине, исключив подстроки. Не могу опубликовать код, потому что я написал это для своего работодателя.

Carlos Rendon
23 ноября 2009 в 17:21
1

Ваш StringToByteArray () не работает, если у вас нечетное количество шестнадцатеричных символов. Это легко исправить, если заполнить нечетные строки "0" впереди.

David Boike
9 марта 2010 в 19:01
90

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

iJK
20 апреля 2010 в 14:39
0

Первый пример возвращает другое значение, чем второй. Кто-нибудь может объяснить почему?

McKay
22 июня 2012 в 19:22
0

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

Jon Skeet
15 января 2013 в 07:09
2

См. Coderhelper.com/a/14332574/22656 для версии, в которой не используется подстрока.

00jt
28 января 2013 в 15:15
0

@DavidBoike - "F" недопустимая шестнадцатеричная строка? Разве это не то же самое, что "0F"? Таким образом, у вас может быть шестнадцатеричная строка с нечетным количеством символов.

David Boike
28 января 2013 в 15:35
7

@ 00jt Вы должны сделать предположение, что F == 0F. Либо это то же самое, что и 0F, либо вход был обрезан, и F на самом деле является началом чего-то, чего вы не получили. Это зависит от вашего контекста, чтобы делать эти предположения, но я считаю, что функция общего назначения должна отклонять нечетные символы как недопустимые, а не делать это предположение для вызывающего кода.

00jt
30 января 2013 в 19:25
11

@DavidBoike Вопрос не имел НИЧЕГО общего с «как обрабатывать возможно обрезанные значения потока». Он говорит о String. String myValue = 10.ToString («X»); myValue - это «A», а не «0A». Теперь прочтите эту строку обратно в байты, ой, вы ее сломали.

Rushyo
24 октября 2013 в 10:18
1

В вопросе прямо указано, что они хотели бы, чтобы он действительно конвертировался обратно. F! = 0F, поэтому вы получите другой результат. Кроме того, делать ненужные предположения - это вообще плохая практика. Ваш пример глупый, вы бы использовали ToString ("X2"). Ваш пример идентичен тому, что если вы используете ToString ("X3"), он не проходит через функции шестнадцатеричного анализа. Конечно, не потому, что вы его нестандартно закодировали.

dumbledad
19 июля 2014 в 20:43
0

В .Net Micro Framework нет ни Convert.ToByte, принимающего два аргумента, ни StringReader, было бы здорово увидеть StringToByteArray в ответе без тех, которые используются.

Hamed Zakery Miab
14 января 2015 в 06:12
0

var str = System.Text.Encoding.Default.GetString (результат);

kanchirk
18 марта 2015 в 17:09
0

@Link указывает на спам. thinksharp.org/hex-string-to-byte-array-converter/

RBT
21 ноября 2016 в 07:18
0

@DavidBoike Я никогда не знал, что такое клев. Ваш комментарий заставил меня задуматься. Спасибо.

Maarten Bodewes
11 июня 2018 в 00:40
1

StringToByteArray ужасное название для этой функции. Назовите его EncodeHex или DecodeHex или создайте класс Hex и вставьте методы Encode или Decode. Вы также можете декодировать base64, кодировать как UTF-8 или UTF-16 и называть его StringToByteArray. Где-то в имени нужно вставить часть Hex.

Jake Gaston
13 августа 2020 в 21:18
0

желаю, чтобы в нем были данные для примера.

avatar
7 апреля 2022 в 11:14
0

Вот мое чисто двоичное решение без необходимости поиска в библиотеке, а также с поддержкой верхнего/нижнего регистра:

public static String encode(byte[] bytes, boolean uppercase) {
    char[] result = new char[2 * bytes.length];
    for (int i = 0; i < bytes.length; i++) {
        byte word = bytes[i];
        byte left = (byte) ((0XF0 & word) >>> 4);
        byte right = (byte) ((byte) 0X0F & word);

        int resultIndex = i * 2;
        result[resultIndex] = encode(left, uppercase);
        result[resultIndex + 1] = encode(right, uppercase);
    }
    return new String(result);
}

public static char encode(byte value, boolean uppercase) {
    int characterCase = uppercase ? 0 : 32;
    if (value > 15 || value < 0) {
        return '0';
    }
    if (value > 9) {
        return (char) (value + 0x37 | characterCase);
    }
    return (char) (value + 0x30);
}
avatar
30 июля 2021 в 19:56
1

Обновление Dotnet 5

Чтобы преобразовать из byte[] (байтовый массив) в шестнадцатеричный string, используйте:

System.Convert.ToHexString

var myBytes = new byte[100];
var myString = System.Convert.ToHexString(myBytes);

Для преобразования из шестнадцатеричного string в byte[] используйте:

System.Convert.FromHexString

var myString  = "E10B116E8530A340BCC7B3EAC208487B";
var myBytes = System.Convert.FromHexString(myString);
avatar
Rosei
24 июля 2021 в 15:48
2

Тесты: шестнадцатеричная строка в байтовый массив

Я заметил, что большинство тестов было выполнено на функциях, которые преобразуют массив байтов в строку Hex. Итак, в этом посте я сосредоточусь на другой стороне: функциях, преобразующих шестнадцатеричную строку в байтовый массив. Если вас интересует только результат, вы можете перейти к разделу Сводка . Файл с тестовым кодом находится в конце сообщения.

Этикетки

Я хотел бы назвать функцию из принятого ответа (Томалака) StringToByteArrayV1 или сократить ее до V1. остальные функции будут названы так же: V2, V3, V4, ... и т. д.

Указатель функций участия

  • StringToByteArrayV1 от Tomalak (принятый ответ)
  • StringToByteArrayV2 от Mykroft (с использованием SoapHexBinary)
  • StringToByteArrayV3 от drphrozen (справочная таблица)
  • StringToByteArrayV4 от CoperNick (манипулирование байтами)
  • StringToByteArrayV5_1, автор: Chris F (Манипулирование байтами)
  • StringToByteArrayV5_2 от Chris F (V5_1 + улучшил его на основе комментария Амира Резаи)
  • StringToByteArrayV5_3 от Chris F (V5_2 + улучшил его на основе комментария Бена Фойгта) (вы можете увидеть его окончательную форму в этом сообщении, опубликованном тестовом коде)
  • StringToByteArrayV6, автор Ben Mosher (Byte Manipulation)
  • StringToByteArrayV7 от Maratius (манипуляция байтами - безопасная версия)
  • StringToByteArrayV8 от Maratius (манипулирование байтами - небезопасная версия)
  • StringToByteArrayV9 от Geograph
  • StringToByteArrayV10, автор - AlejandroAlis
  • StringToByteArrayV11, автор - Фредрик Ху
  • StringToByteArrayV12, автор - Маартен Бодевес
  • StringToByteArrayV13 от Клауса Андерсена
  • StringToByteArrayV14 от Стаса Макутина
  • StringToByteArrayV15 от JJJ
  • StringToByteArrayV16 от JamieSee
  • StringToByteArrayV17, пробел
  • StringToByteArrayV18, автор Грегори Морс
  • StringToByteArrayV19 от Рика
  • StringToByteArrayV20 от SandRock
  • StringToByteArrayV21, автор Пол

Тест на правильность

Я проверил правильность, передав все 256 возможных значений 1 байта, а затем проверив вывод, чтобы убедиться в правильности. Результат:

  • В V18 есть проблема со строками, начинающимися с «00» (см. Комментарий Роджера Стюарта). кроме этого, он проходит все тесты.
  • если шестнадцатеричные буквы алфавита являются прописными: все функции успешно пройдены
  • если шестнадцатеричные буквы алфавита являются строчными, то следующие функции завершились ошибкой: V5_1, V5_2, v7, V8, V15, V19

примечание: V5_3 решает эту проблему (для V5_1 и V5_2)

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

Я провел тесты производительности с использованием класса Stopwatch.

  • Исполнение для длинных струн
input length: 10,000,000 bytes
runs: 100
average elapsed time per run:
V1 = 136.4ms
V2 = 104.5ms
V3 = 22.0ms
V4 = 9.9ms
V5_1 = 10.2ms
V5_2 = 9.0ms
V5_3 = 9.3ms
V6 = 18.3ms
V7 = 9.8ms
V8 = 8.8ms
V9 = 10.2ms
V10 = 19.0ms
V11 = 12.2ms
V12 = 27.4ms
V13 = 21.8ms
V14 = 12.0ms
V15 = 14.9ms
V16 = 15.3ms
V17 = 9.5ms
V18 got excluded from this test, because it was very slow when using very long string
V19 = 222.8ms
V20 = 66.0ms
V21 = 15.4ms

V1 average ticks per run: 1363529.4
V2 is more fast than V1 by: 1.3 times (ticks ratio)
V3 is more fast than V1 by: 6.2 times (ticks ratio)
V4 is more fast than V1 by: 13.8 times (ticks ratio)
V5_1 is more fast than V1 by: 13.3 times (ticks ratio)
V5_2 is more fast than V1 by: 15.2 times (ticks ratio)
V5_3 is more fast than V1 by: 14.8 times (ticks ratio)
V6 is more fast than V1 by: 7.4 times (ticks ratio)
V7 is more fast than V1 by: 13.9 times (ticks ratio)
V8 is more fast than V1 by: 15.4 times (ticks ratio)
V9 is more fast than V1 by: 13.4 times (ticks ratio)
V10 is more fast than V1 by: 7.2 times (ticks ratio)
V11 is more fast than V1 by: 11.1 times (ticks ratio)
V12 is more fast than V1 by: 5.0 times (ticks ratio)
V13 is more fast than V1 by: 6.3 times (ticks ratio)
V14 is more fast than V1 by: 11.4 times (ticks ratio)
V15 is more fast than V1 by: 9.2 times (ticks ratio)
V16 is more fast than V1 by: 8.9 times (ticks ratio)
V17 is more fast than V1 by: 14.4 times (ticks ratio)
V19 is more SLOW than V1 by: 1.6 times (ticks ratio)
V20 is more fast than V1 by: 2.1 times (ticks ratio)
V21 is more fast than V1 by: 8.9 times (ticks ratio)
  • Исполнение V18 для длинных струн
V18 took long time at the previous test, 
so let's decrease length for it:  
input length: 1,000,000 bytes
runs: 100
average elapsed time per run: V1 = 14.1ms , V18 = 146.7ms
V1 average ticks per run: 140630.3
V18 is more SLOW than V1 by: 10.4 times (ticks ratio)
  • Исполнение для коротких струн
input length: 100 byte
runs: 1,000,000
V1 average ticks per run: 14.6
V2 is more fast than V1 by: 1.4 times (ticks ratio)
V3 is more fast than V1 by: 5.9 times (ticks ratio)
V4 is more fast than V1 by: 15.7 times (ticks ratio)
V5_1 is more fast than V1 by: 15.1 times (ticks ratio)
V5_2 is more fast than V1 by: 18.4 times (ticks ratio)
V5_3 is more fast than V1 by: 16.3 times (ticks ratio)
V6 is more fast than V1 by: 5.3 times (ticks ratio)
V7 is more fast than V1 by: 15.7 times (ticks ratio)
V8 is more fast than V1 by: 18.0 times (ticks ratio)
V9 is more fast than V1 by: 15.5 times (ticks ratio)
V10 is more fast than V1 by: 7.8 times (ticks ratio)
V11 is more fast than V1 by: 12.4 times (ticks ratio)
V12 is more fast than V1 by: 5.3 times (ticks ratio)
V13 is more fast than V1 by: 5.2 times (ticks ratio)
V14 is more fast than V1 by: 13.4 times (ticks ratio)
V15 is more fast than V1 by: 9.9 times (ticks ratio)
V16 is more fast than V1 by: 9.2 times (ticks ratio)
V17 is more fast than V1 by: 16.2 times (ticks ratio)
V18 is more fast than V1 by: 1.1 times (ticks ratio)
V19 is more SLOW than V1 by: 1.6 times (ticks ratio)
V20 is more fast than V1 by: 1.9 times (ticks ratio)
V21 is more fast than V1 by: 11.4 times (ticks ratio)

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

Перед использованием любого из следующего кода рекомендуется прочитать раздел об отказе от ответственности в этом сообщении. https://github.com/Ghosticollis/performance-tests/blob/main/MTestPerformance.cs

Сводка

Я рекомендую использовать одну из следующих функций из-за хорошей производительности и поддержки как верхнего, так и нижнего регистра:

  • StringToByteArrayV4 от CoperNick
  • StringToByteArrayV9 от Geograph
  • StringToByteArrayV17, пробел
  • StringToByteArrayV5_3, в основном, Крис Ф. (он основан на V5_1, но я улучшил его на основе комментариев Амира Резаи и Бена Фойгта).

Вот окончательная форма V5_3:

static byte[] HexStringToByteArrayV5_3(string hexString) {
    int hexStringLength = hexString.Length;
    byte[] b = new byte[hexStringLength / 2];
    for (int i = 0; i < hexStringLength; i += 2) {
        int topChar = hexString[i];
        topChar = (topChar > 0x40 ? (topChar & ~0x20) - 0x37 : topChar - 0x30) << 4;
        int bottomChar = hexString[i + 1];
        bottomChar = bottomChar > 0x40 ? (bottomChar & ~0x20) - 0x37 : bottomChar - 0x30;
        b[i / 2] = (byte)(topChar + bottomChar);
    }
    return b;
}

Отказ от ответственности

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

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

JosephDoggie
7 июля 2021 в 16:55
1

Вау, это много усилий!

avatar
Ali Zahid
2 марта 2021 в 13:49
4

.NET 5 добавил метод Convert.ToHexString.

Для тех, кто использует старую версию .NET

internal static class ByteArrayExtensions
{
    
    public static string ToHexString(this byte[] bytes, Casing casing = Casing.Upper)
    {
        Span<char> result = stackalloc char[0];
        if (bytes.Length > 16)
        {
            var array = new char[bytes.Length * 2];
            result = array.AsSpan();
        }
        else
        {
            result = stackalloc char[bytes.Length * 2];
        }

        int pos = 0;
        foreach (byte b in bytes)
        {
            ToCharsBuffer(b, result, pos, casing);
            pos += 2;
        }

        return result.ToString();
    }

    private static void ToCharsBuffer(byte value, Span<char> buffer, int startingIndex = 0, Casing casing = Casing.Upper)
    {
        uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U;
        uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing;

        buffer[startingIndex + 1] = (char)(packedResult & 0xFF);
        buffer[startingIndex] = (char)(packedResult >> 8);
    }
}

public enum Casing : uint
{
    // Output [ '0' .. '9' ] and [ 'A' .. 'F' ].
    Upper = 0,

    // Output [ '0' .. '9' ] and [ 'a' .. 'f' ].
    Lower = 0x2020U,
}

Адаптировано из репозитория .NET https://github.com/dotnet/runtime/blob/v5.0.3/src/libraries/System.Private.CoreLib/src/System/Convert.cs https://github.com/dotnet/runtime/blob/v5.0.3/src/libraries/Common/src/System/HexConverter.cs

avatar
23 октября 2020 в 10:58
5

Самый быстрый метод для людей старой школы ... скучаю по указателям

    static public byte[] HexStrToByteArray(string str)
    {
        byte[] res = new byte[(str.Length % 2 != 0 ? 0 : str.Length / 2)]; //check and allocate memory
        for (int i = 0, j = 0; j < res.Length; i += 2, j++) //convert loop
            res[j] = (byte)((str[i] % 32 + 9) % 25 * 16 + (str[i + 1] % 32 + 9) % 25);
        return res;
    }
avatar
balrob
23 октября 2020 в 06:45
20

Начиная с .NET 5 RC2 вы можете использовать:

Доступны перегрузки, которые принимают параметры диапазона.

avatar
12 сентября 2020 в 20:33
1

Для удобства копирования и вставки я объединил несколько ответов в класс:

/// <summary>
/// Extension methods to quickly convert byte array to string and back.
/// </summary>
public static class HexConverter
{
    /// <summary>
    /// Map values to hex digits
    /// </summary>
    private static readonly char[] HexDigits =
        {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
        };

    /// <summary>
    /// Map 56 characters between ['0', 'F'] to their hex equivalents, and set invalid characters
    /// such that they will overflow byte to fail conversion.
    /// </summary>
    private static readonly ushort[] HexValues =
        {
            0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
            0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
            0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x000A, 0x000B,
            0x000C, 0x000D, 0x000E, 0x000F
        };

    /// <summary>
    /// Empty byte array 
    /// </summary>
    private static readonly byte[] Empty = new byte[0];

    /// <summary>
    /// Convert a byte array to a hexadecimal string.
    /// </summary>
    /// <param name="bytes">
    /// The input byte array.
    /// </param>
    /// <returns>
    /// A string of hexadecimal digits.
    /// </returns>
    public static string ToHexString(this byte[] bytes)
    {
        var c = new char[bytes.Length * 2];
        for (int i = 0, j = 0; i < bytes.Length; i++)
        {
            c[j++] = HexDigits[bytes[i] >> 4];
            c[j++] = HexDigits[bytes[i] & 0x0F];
        }

        return new string(c);
    }

    /// <summary>
    /// Parse a string of hexadecimal digits into a byte array.
    /// </summary>
    /// <param name="hexadecimalString">
    /// The hexadecimal string.
    /// </param>
    /// <returns>
    /// The parsed <see cref="byte[]"/> array.
    /// </returns>
    /// <exception cref="ArgumentException">
    /// The input string either contained invalid characters, or was of an odd length.
    /// </exception>
    public static byte[] ToByteArray(string hexadecimalString)
    {
        if (!TryParse(hexadecimalString, out var value))
        {
            throw new ArgumentException("Invalid hexadecimal string", nameof(hexadecimalString));
        }

        return value;
    }

    /// <summary>
    /// Parse a hexadecimal string to bytes
    /// </summary>
    /// <param name="hexadecimalString">
    /// The hexadecimal string, which must be an even number of characters.
    /// </param>
    /// <param name="value">
    /// The parsed value if successful.
    /// </param>
    /// <returns>
    /// True if successful.
    /// </returns>
    public static bool TryParse(string hexadecimalString, out byte[] value)
    {
        if (hexadecimalString.Length == 0)
        {
            value = Empty;
            return true;
        }

        if (hexadecimalString.Length % 2 != 0)
        {
            value = Empty;
            return false;
        }

        try
        {

            value = new byte[hexadecimalString.Length / 2];
            for (int i = 0, j = 0; j < hexadecimalString.Length; i++)
            {
                value[i] = (byte)((HexValues[hexadecimalString[j++] - '0'] << 4)
                                  | HexValues[hexadecimalString[j++] - '0']);
            }

            return true;
        }
        catch (OverflowException)
        {
            value = Empty;
            return false;
        }
    }
}
avatar
29 апреля 2020 в 01:10
-2

С Java 8 мы можем использовать Byte.toUnsignedInt

public static String convertBytesToHex(byte[] bytes) {
    StringBuilder result = new StringBuilder();
    for (byte byt : bytes) {
        int decimal = Byte.toUnsignedInt(byt);
        String hex = Integer.toHexString(decimal);
        result.append(hex);
    }
    return result.toString();
}
Maarten Bodewes
2 мая 2020 в 00:35
1

Неверно, toHexString может возвращать только один символ вместо двух.

avatar
10 марта 2020 в 21:23
2

Существует еще не упомянутое простое однострочное решение, которое преобразует шестнадцатеричные строки в байтовые массивы (здесь нас не волнует отрицательная интерпретация, поскольку это не имеет значения):

BigInteger.Parse(str, System.Globalization.NumberStyles.HexNumber).ToByteArray().Reverse().ToArray();
Roger Stewart
11 марта 2020 в 12:52
0

Это не сохраняет начальные байты 0. Например, строка "000080" приводит к однобайтовому массиву { 0x80 }, а не к ожидаемому трехбайтовому массиву { 0x00, 0x00, 0x80 }

Gregory Morse
12 марта 2020 в 13:33
0

Да, я полагаю, что что-то вроде Enumerable.Repeat <byte> (0, (len (str) / 2 - len (bigIntBytes)). Concat (bigIntBytes) .ToArray () необходимо в этом случае

Simon Bondo
17 апреля 2020 в 14:28
0

Вместо того, чтобы переворачивать массив, вы можете сделать массив в режиме прямого байта: .ToByteArray(isBigEndian: true)

Gregory Morse
18 апреля 2020 в 16:43
0

Спасибо, Симон, я не знал или хотя бы забыл об этом удобном параметре!

avatar
Mark
26 ноября 2019 в 23:27
4

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

dolmen
20 августа 2013 в 23:49
3

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

avatar
21 ноября 2019 в 13:04
2

Поддерживаемый кратчайший путь и ядро ​​.net:

    public static string BytesToString(byte[] ba) =>
        ba.Aggregate(new StringBuilder(32), (sb, b) => sb.Append(b.ToString("X2"))).ToString();
Andy
9 марта 2021 в 02:50
1

Это прекрасно, если ba составляет 16 байт или меньше. Но он должен быть new StringBuilder(ba.Length * 2), чтобы эффективно обрабатывать массив байтов любой длины.

avatar
tomasz_kajetan_stanczak
1 ноября 2019 в 23:37
1
    // a safe version of the lookup solution:       

    public static string ByteArrayToHexViaLookup32Safe(byte[] bytes, bool withZeroX)
    {
        if (bytes.Length == 0)
        {
            return withZeroX ? "0x" : "";
        }

        int length = bytes.Length * 2 + (withZeroX ? 2 : 0);
        StateSmall stateToPass = new StateSmall(bytes, withZeroX);
        return string.Create(length, stateToPass, (chars, state) =>
        {
            int offset0x = 0;
            if (state.WithZeroX)
            {
                chars[0] = '0';
                chars[1] = 'x';
                offset0x += 2;
            }

            Span<uint> charsAsInts = MemoryMarshal.Cast<char, uint>(chars.Slice(offset0x));
            int targetLength = state.Bytes.Length;
            for (int i = 0; i < targetLength; i += 1)
            {
                uint val = Lookup32[state.Bytes[i]];
                charsAsInts[i] = val;
            }
        });
    }

    private struct StateSmall
    {
        public StateSmall(byte[] bytes, bool withZeroX)
        {
            Bytes = bytes;
            WithZeroX = withZeroX;
        }

        public byte[] Bytes;
        public bool WithZeroX;
    }
avatar
30 мая 2019 в 12:55
1

Я придумал другой код , допускающий использование дополнительных символов (пробелы, тире ...). Он в основном основан на некоторых достаточно быстрых ответах здесь. Это позволяет разобрать следующий "файл"

00-aa-84-fb
12 32 FF CD
12 00
12_32_FF_CD
1200d5e68a
/// <summary>Reads a hex string into bytes</summary>
public static IEnumerable<byte> HexadecimalStringToBytes(string hex) {
    if (hex == null)
        throw new ArgumentNullException(nameof(hex));

    char c, c1 = default(char);
    bool hasc1 = false;
    unchecked   {
        for (int i = 0; i < hex.Length; i++) {
            c = hex[i];
            bool isValid = 'A' <= c && c <= 'f' || 'a' <= c && c <= 'f' || '0' <= c && c <= '9';
            if (!hasc1) {
                if (isValid) {
                    hasc1 = true;
                }
            } else {
                hasc1 = false;
                if (isValid) {
                    yield return (byte)((GetHexVal(c1) << 4) + GetHexVal(c));
                }
            }

            c1 = c;
        } 
    }
}

/// <summary>Reads a hex string into a byte array</summary>
public static byte[] HexadecimalStringToByteArray(string hex)
{
    if (hex == null)
        throw new ArgumentNullException(nameof(hex));

    var bytes = new List<byte>(hex.Length / 2);
    foreach (var item in HexadecimalStringToBytes(hex)) {
        bytes.Add(item);
    }

    return bytes.ToArray();
}

private static byte GetHexVal(char val)
{
    return (byte)(val - (val < 0x3A ? 0x30 : val < 0x5B ? 0x37 : 0x57));
    //                   ^^^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^   ^^^^
    //                       digits 0-9       upper char A-Z     a-z
}

Пожалуйста, используйте полный код при копировании. Включены модульные тесты.

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

avatar
astrada
5 июля 2018 в 04:12
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());
    }
avatar
tne
5 июля 2018 в 04:10
22

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

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

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

Да!

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

209113288F93A9AB8E474EA78D899AFDBB874355

Удачи! (Но оптимизируйте с помощью модерации.)

Priya Jagtap
21 апреля 2020 в 20:09
1

error: {"Не удалось найти узнаваемые цифры."}

avatar
Baget
5 июля 2018 в 04:07
67

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

Sly Gryphon
28 июня 2011 в 06:49
15

Отвечает только на половину вопроса.

Sawan
25 декабря 2012 в 09:12
4

Где вторая часть ответа?

Franz D.
13 мая 2021 в 16:10
1

Я надеюсь, что преобразование 256 в "FF" просто опечатка ...

avatar
Mykroft
5 июля 2018 в 04:07
252

Есть класс под названием 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();
}
Sly Gryphon
28 июня 2011 в 06:48
37

SoapHexBinary доступен из .NET 1.0 и находится в mscorlib. Несмотря на забавное пространство имен, оно делает именно то, что задан вопросом.

Carter Medlin
31 октября 2011 в 17:10
4

Отличная находка! Обратите внимание, что вам нужно будет дополнить нечетные строки начальным 0 для GetStringToBytes, как и в другом решении.

mfloryan
26 января 2012 в 13:42
0

Вы видели реализацию мысли? У принятого ответа есть лучший ИМХО.

Mykroft
26 января 2012 в 19:20
0

Вы имеете в виду реализацию SoapHexBinary? Если да, то что делает его хуже, чем реализация в принятом ответе?

Jeremy
29 апреля 2012 в 04:40
6

Интересно увидеть реализацию Mono здесь: github.com/mono/mono/blob/master/mcs/class/corlib/…

Ben Mosher
22 мая 2012 в 16:39
0

В моих тестах (которые я собираюсь бросить в ответ) Mono's impl. примерно на 10% быстрее, чем SoapHexBinary, и в 16 раз медленнее моего ...

juFo
11 марта 2020 в 09:12
7

SoapHexBinary не поддерживается в .NET Core / .NET Standard ...

Gaspa79
4 мая 2021 в 18:01
0

Да, это довольно прискорбно (это не в ядре .NET). Было довольно хорошо до сегодняшнего дня

avatar
14 мая 2018 в 12:53
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();
avatar
drphrozen
21 июня 2017 в 23:34
18

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

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

dolmen
21 августа 2013 в 00:05
2

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

avatar
ClausAndersen
21 июня 2017 в 23:33
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]));
}

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

avatar
spacepille
21 июня 2017 в 23:24
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;
}
avatar
Craig Poulton
21 июня 2017 в 23:20
11

Зачем усложнять? В Visual Studio 2008 это просто:

C #:

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

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Ricky
4 августа 2016 в 06:28
3

причина в производительности, когда вам нужно высокопроизводительное решение. :)

avatar
Ben Mosher
21 июня 2017 в 23:10
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 другим методом, но это был самый быстрый способ.

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

Ben Mosher
22 мая 2012 в 17:01
1

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

avatar
CoperNick
21 июня 2017 в 23:07
18

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

avatar
JamieSee
21 июня 2017 в 23:05
1

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

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

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

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

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

  1. Штрафы за индексацию строки для выполнения поиска по сравнению с Массив char имеет значение в тесте большого файла.
  2. Штрафы за использование 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: в среднем 41588,64 тика (более 1000 запусков), 21,2X

ByteArrayToHexViaLookup: 55 509,56 средних тиков (более 1000 прогонов), 15,9X

ByteArrayToHexViaByteManipulation: 65 349,12 средних тиков (более 1000 прогонов), 13,5X

ByteArrayToHexViaLookupAndShift: в среднем 86926,87 тиков (более 1000 пробеги), 10,2X

ByteArrayToHexStringViaBitConverter: в среднем 139 353,73 тиков (более 1000 прогонов), 6,3X

ByteArrayToHexViaSoapHexBinary: 314598,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


avatar
Olipro
21 июня 2017 в 23:02
-1

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

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

avatar
Waleed Eissa
21 июня 2017 в 22:58
57

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

private static string ByteArrayToHex(byte[] barray)
{
    char[] c = new char[barray.Length * 2];
    byte b;
    for (int i = 0; i < barray.Length; ++i)
    {
        b = ((byte)(barray[i] >> 4));
        c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
        b = ((byte)(barray[i] & 0xF));
        c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
    }
    return new string(c);
}

Источник: сообщение форума byte [] Массив в шестнадцатеричную строку (см. Сообщение PZahra). Я немного изменил код, чтобы удалить префикс 0x.

Я провел небольшое тестирование производительности кода, и это было почти в восемь раз быстрее, чем при использовании BitConverter.ToString () (самый быстрый согласно сообщению Патриджа).

Chochos
16 октября 2009 в 17:36
0

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

Sly Gryphon
28 июня 2011 в 06:50
8

Отвечает только на половину вопроса.

Jonesome Reinstate Monica
6 февраля 2012 в 04:26
0

Это здорово, потому что работает практически с любой версией NET, включая NETMF. Победитель!

Brendten Eickstaedt
10 октября 2012 в 16:08
2

Принятый ответ предоставляет 2 отличных метода HexToByteArray, которые представляют вторую половину вопроса. Решение Waleed отвечает на текущий вопрос о том, как это сделать, не создавая при этом огромное количество строк.

jjxtra
15 октября 2013 в 17:24
0

Копирует и перераспределяет новую строку (c) или достаточно умен, чтобы знать, когда можно просто обернуть char []?

Brian Reichle
21 января 2014 в 06:51
0

@PsychoDad, он копирует. Строка должна быть неизменной, в то время как char [] может измениться после создания строки.

Patrick
27 сентября 2018 в 14:59
0

@SlyGryphon На самом деле, если вы прокрутите вниз от упомянутого сообщения на форуме, я фактически предоставил другую сторону этого, которая с тех пор была обновлена ​​здесь: coderhelper.com/a/22158486/278889

avatar
Will Dean
21 июня 2017 в 22:56
107

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

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

Nestor
25 ноября 2009 в 15:04
22

Еще короче: String.Concat (Array.ConvertAll (bytes, x => x.ToString ("X2"))

Allon Guralnek
16 июня 2011 в 06:39
15

Еще короче: String.Concat (bytes.Select (b => b.ToString ("X2"))) [.NET4]

Sly Gryphon
28 июня 2011 в 06:50
14

Отвечает только на половину вопроса.

Polyfun
17 октября 2014 в 11:42
1

Зачем второму .Net 4? String.Concat находится в .Net 2.0.

Austin_Anderson
24 октября 2017 в 19:47
2

эти циклы в стиле 90-х обычно быстрее, но на достаточно незначительную величину, поэтому в большинстве случаев это не имеет значения. Тем не менее, стоит упомянуть

avatar
Pure.Krome
21 июня 2017 в 22:51
5

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

jww
16 февраля 2017 в 19:08
0

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

avatar
Nicholas Petersen
23 мая 2017 в 12:03
1

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

    /// <summary>
    /// Converts the byte array to a hex string very fast. Excellent job
    /// with code lightly adapted from 'community wiki' here: https://coderhelper.com/a/14333437/264031
    /// (the function was originally named: ByteToHexBitFiddle). Now allows a native lowerCase option
    /// to be input and allows null or empty inputs (null returns null, empty returns empty).
    /// </summary>
    public static string ToHexString(this byte[] bytes, bool lowerCase = false)
    {
        if (bytes == null)
            return null;
        else if (bytes.Length == 0)
            return "";

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

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

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

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

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

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

    public static string ToHexString(this IEnumerable<byte> bytes, bool lowerCase = false)
    {
        if (bytes == null)
            return null;
        byte[] arr = bytes.ToArray();
        return arr.ToHexString(lowerCase);
    }
avatar
2 мая 2017 в 11:12
1
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-компиляции и снижение производительности сборщика мусора. Я хотел бы переписать его, чтобы получить больше информации, но у меня не было на это времени.

avatar
20 мая 2016 в 16:23
2

Другой способ - использовать 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);
}
avatar
CodesInChaos
24 марта 2016 в 14:27
148

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

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

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

Ф'нглуи мглв'нафх Ктулху Р'льех wgah'nagl fhtagn


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

Объяснение странной игры с битами:

  1. bytes[i] >> 4 извлекает старший полубайт байта
    bytes[i] & 0xF извлекает младший полубайт байта
  2. b - 10
    < 0 для значений b < 10, которое станет десятичной цифрой
    >= 0 для значений b > 10, которое станет буквой от A до F.
  3. Использование i >> 31 для 32-битного целого числа со знаком извлекает знак благодаря расширению знака. Это будет -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], поэтому я выбрал этот вариант.
  • Создание b типа int позволяет выполнять ненужные преобразования из и в байты.
AaA
18 января 2013 в 07:56
11

И от hex string до byte[] array?

Edward
2 августа 2013 в 20:41
15

+1 за правильное цитирование вашего источника после того, как вы применили эту черную магию. Приветствую Ктулху.

dolmen
20 августа 2013 в 23:43
0

Лучший ответ (для части вопроса с шестнадцатеричным кодированием)!

Syaiful Nizam Yahya
6 ноября 2013 в 10:14
6

А как насчет строки в байт []?

Syaiful Nizam Yahya
7 ноября 2013 в 03:59
0

Я хотел сказать, что если у меня "0x1B", как мне преобразовать его в байт?

eXavier
6 января 2014 в 17:36
10

Хороший! Для тех, кому нужен вывод в нижнем регистре, выражение, очевидно, меняется на 87 + b + (((b-10)>>31)&-39)

Maarten Bodewes
20 января 2014 в 23:17
0

Теперь у меня есть это на Java и C # для кодирования и декодирования, чтобы продемонстрировать мою «темную магию» (одиночный цикл, без ветвей, кроме последней для шестнадцатеричных ошибок). Конечно, помимо вашего краткого кода, я люблю иногда ломать голову.

CoolOppo
10 июня 2015 в 02:46
0

@AaA вы хотите преобразовать его в массив байтовых массивов? ;)

AaA
10 июня 2015 в 02:59
0

@CoolOppo, я не уверен, что вы имеете в виду, но шестнадцатеричная строка имеет формат "123456789ABCDEF", что означает, что каждые два символа преобразуются в one byte

CoolOppo
10 июня 2015 в 03:09
2

@AaA Вы сказали "byte[] array", что буквально означает массив байтовых массивов или byte[][]. Я просто подшучивал.

David R Tribble
16 июля 2018 в 15:44
0

Почему бы просто не использовать 'A'-0xA вместо 55? Пусть компилятор во всем разберется; магические числа, такие как 55, трудны для понимания людьми и подвержены ошибкам.

CodesInChaos
16 июля 2018 в 17:43
0

@DavidRTribble Потому что я не хочу брать на себя венок Великого Лорда Ктулху.

David R Tribble
23 июля 2018 в 15:47
0

@CodesInChaos - Я знаю, вы имели в виду гнев . Но видите ли, это проблема с символом 101, который я, конечно, написал бы как 'e'. ;-)

Oleg Skripnyak
25 мая 2019 в 08:41
0

@CodesInChaos Удивительно, ни разу, хотя этот минус не играет в такую ​​игру с оператором "и", никогда не использовал его. Похоже на магию. Что такое фон? По моему мнению, минус в таком выражении должен означать, что установлен крайний левый бит, а обычное 'and' должно давать минус, если источник только минус, но он другой: 3 & 9 == 1, все в порядке, но 3 & -9 = = 3 и (-3) & (-9) == -11, какого хрена?

juFo
11 марта 2020 в 09:40
0

Можно ли использовать (ReadOnly) Span здесь?

avatar
17 декабря 2015 в 11:15
6

Обратная функция для кода Валида Эйссы (шестнадцатеричная строка в байтовый массив):

    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);
    }
avatar
JJJ
16 июля 2014 в 10:53
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;
}
CoperNick
29 июля 2013 в 10:26
1

HexStringToByteArray ("09") возвращает 0x02, что плохо.

avatar
CodesInChaos
21 июня 2014 в 17:00
77

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

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;
}
Raif Atef
5 ноября 2014 в 13:13
1

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

CodesInChaos
7 ноября 2014 в 12:09
1

@RaifAtef Здесь важен не порядок кусочков. Но порядок 16-битных слов в 32-битном целом числе. Но я подумываю переписать его, чтобы тот же код мог работать независимо от порядка байтов.

Raif Atef
7 ноября 2014 в 13:26
1

Перечитывая код, я думаю, что вы сделали это, потому что, когда вы позже преобразуете char * в uint * и назначите его (при генерации шестнадцатеричного символа), среда выполнения / ЦП перевернут байты (поскольку uint не обрабатывается так же, как 2 отдельных 16-битных символа), поэтому вы предварительно переворачиваете их для компенсации. Я прав ? Порядок байтов сбивает с толку :-).

Joe Amenta
9 января 2016 в 12:24
1

Хорошо, я укушу - какое преимущество в том, чтобы закрепить _lookup32Unsafe на неопределенный срок вместо того, чтобы просто выполнять третий оператор fixed и позволять GC перемещать массив в его основное содержимое, когда этот метод не работает?

CodesInChaos
9 января 2016 в 15:32
1

@JoeAmenta Не уверен, есть ли в этом случае какое-либо измеримое преимущество. Возможно, я просто не думал об этой альтернативе при написании этого кода.

Narvalex
8 марта 2017 в 17:28
7

Это просто ответ на половину вопроса ... Как насчет того, чтобы от шестнадцатеричной строки к байтам?

TamaMcGlinn
30 января 2018 в 10:33
1

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

CodesInChaos
30 января 2018 в 11:00
1

@TamaMcGlinn В безопасной реализации для внутреннего представления используется прямой порядок байтов, но не делается никаких предположений о машинном порядке байтов. Так что он все равно должен работать на машинах с прямым порядком байтов (возможно, немного медленнее). В отличие от этого небезопасный вариант повторно интерпретирует два 16-битных слова как одно 32-битное слово, поэтому он должен быть осторожен с порядком байтов хоста.

Konrad
4 декабря 2019 в 13:12
8

@CodesInChaos Интересно, можно ли теперь использовать Span вместо unsafe ??

to11mtm
16 января 2021 в 22:37
1

@Konrad, наверное, мог бы; одно интересное влияние заключается в том, что в этот момент static ReadOnlySpan, инициализированный массивом литералов, становится статическими данными в DLL.

avatar
20 января 2014 в 23:38
3

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

Maarten Bodewes
20 января 2014 в 23:46
1

Хм, мне действительно нужно оптимизировать это для Char[] и использовать Char внутри вместо целых чисел ...

Peteter
12 июня 2019 в 16:50
1

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

avatar
Maratius
20 декабря 2013 в 06:33
7

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

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

И, конечно же, тест должен быть скомпилирован в выпуске (с оптимизацией) и с отключенной опцией отладки «Подавить оптимизацию JIT» (то же самое для «Включить только мой код», если код должен быть отлаживаемым).

avatar
30 августа 2013 в 23:53
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
avatar
23 августа 2013 в 07:41
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);
    }
avatar
16 декабря 2012 в 20:40
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);
    }
CodesInChaos
15 января 2013 в 09:32
2

На самом деле это медленнее, чем другие подходы, основанные на поиске в таблице (по крайней мере, в моих тестах). Использование != вместо < нарушает некоторые шаблоны оптимизации JIT, а дополнительный счетчик для Offset также кажется дорогостоящим.

avatar
25 мая 2012 в 17:38
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();
    }
avatar
9 января 2012 в 17:29
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;
}
avatar
27 октября 2011 в 03:01
-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
Brian Reichle
2 декабря 2011 в 13:50
3

Что заставляет вас думать, что? Вы создаете новый строковый объект для каждого байта в буфере, и вы не устанавливаете предварительный размер строителя строк (что может привести к многократному изменению размера буфера на больших массивах).

Behrooz
16 декабря 2012 в 21:44
0

Простое английское преобразование байтов :)

avatar
16 июня 2011 в 20:33
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;          
}
avatar
10 апреля 2011 в 00:56
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 + удаление символов '-'.

dolmen
20 августа 2013 в 23:53
4

Если производительность имеет значение, вы не стали бы использовать Math.DivRem для разделения байта на два полубайта.

Thomas Levesque
21 августа 2013 в 00:12
0

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

Søren Boisen
3 августа 2016 в 16:08
1

@ThomasLevesque Реализация DivRem выполняет операцию модуля и деление. Почему вы думаете, что эти операции выполняются вручную? Для меня естественная реализация - github.com/patridge/PerformanceStubs/blob/master/…, которая выполняет битовый сдвиг и логическое и. Эти операции намного дешевле модуля и деления даже на современных процессорах.

avatar
1 июня 2010 в 08:19
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);
  }
avatar
12 января 2010 в 16:51
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;
}
Marc Novakowski
26 января 2010 в 19:17
1

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

Chris F
26 января 2010 в 20:27
0

Это проницательное наблюдение Марк. Код был написан для отмены решения Валида. Вызов ToUpper несколько замедлит алгоритм, но позволит ему обрабатывать альфа-символы нижнего регистра.

Amir Rezaei
12 февраля 2011 в 21:17
3

Convert.ToByte (topChar + bottomChar) можно записать как (byte) (topChar + bottomChar)

Ben Voigt
31 июля 2014 в 22:31
0

Чтобы обработать оба случая без значительного снижения производительности, hexString[i] &= ~0x20;

avatar
14 сентября 2009 в 21:13
2

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

public static String ByteArrayToSQLHexString(byte[] Source)
{
    return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
Andrei Krasutski
7 июня 2019 в 17:37
0

если Source == null или Source.Length == 0 у нас проблема, сэр!

avatar
patridge
8 марта 2009 в 21:56
516

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

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

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

Отказ от ответственности

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

Результаты

  • Поиск по байтам небезопасен (через CodesInChaos) (добавлен в тестовое репо с помощью воздуховода)
    • Текст: 4,727,85 (105,2X)
    • Предложение: 0,28 (99,7X)
  • Поиск по байтам (через CodesInChaos)
    • Текст: 10 853,96 (в 45,8 раза быстрее)
    • Предложение: 0,65 (в 42,7 раза быстрее)
  • Манипуляция байтами 2 (через CodesInChaos)
    • Текст: 12 967,69 (в 38,4 раза быстрее)
    • Предложение: 0,73 (в 37,9 раза быстрее)
  • Манипуляция байтами (через Валида Эйссу)
    • Текст: 16856,64 (в 29,5 раза быстрее)
    • Предложение: 0,70 (в 39,5 раз быстрее)
  • Поиск / сдвиг (через Натана Моинвазири)
    • Текст: 23 201,23 (в 21,4 раза быстрее)
    • Предложение: 1,24 (в 22,3 раза быстрее)
  • Поиск по полубайку (через Брайана Ламберта)
    • Текст: 23 879,41 (в 20,8 раза быстрее)
    • Предложение: 1,15 (в 23,9 раза быстрее)
  • BitConverter (через Томалак)
    • Текст: 113 269,34 (в 4,4 раза быстрее)
    • Предложение: 9,98 (в 2,8 раза быстрее)
  • {SoapHexBinary} .ToString (через Mykroft)
    • Текст: 178 601,39 (в 2,8 раза быстрее)
    • Предложение: 10,68 (в 2,6 раза быстрее)
  • {byte} .ToString ("X2") (с использованием foreach) (получено из ответа Уилла Дина)
    • Текст: 308 805,38 (в 2,4 раза быстрее)
    • Предложение: 16,89 (в 2,4 раза быстрее)
  • {byte} .ToString ("X2") (с использованием {IEnumerable} .Aggregate, требуется System.Linq) (через Mark)
    • Текст: 352 828.20 (в 2,1 раза быстрее)
    • Предложение: 16,87 (в 2,4 раза быстрее)
  • Array.ConvertAll (с использованием string.Join) (через Уилла Дина)
    • Текст: 675 451,57 (в 1,1 раза быстрее)
    • Предложение: 17,95 (в 2,2 раза быстрее)
  • Array.ConvertAll (с использованием string.Concat, требуется .NET 4.0) (через Уилла Дина)
    • Текст: 752078,70 (в 1,0 раза быстрее)
    • Предложение: 18,28 (в 2,2 раза быстрее)
  • {StringBuilder} .AppendFormat (с использованием foreach) (через Tomalak)
    • Текст: 672 115,77 (в 1,1 раза быстрее)
    • Предложение: 36,82 (в 1,1 раза быстрее)
  • {StringBuilder} .AppendFormat (с использованием {IEnumerable} .Aggregate, требуется System.Linq) (получено из ответа Томалака)
    • Текст: 718 380,63 (в 1,0 раза быстрее)
    • Предложение: 39,71 (в 1,0 раз быстрее)

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

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

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

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

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

Обновление (13.01.2010)

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

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

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

Обновление (05.02.2012)

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

Обновление (03.04.2012)

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

Обновление (15.01.2013)

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

Обновление (23.05.2013)

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

Обновление (31.07.2014)

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

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

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

Cristian Diaconescu
24 декабря 2009 в 21:16
0

Не хотите ли вы протестировать код из ответа Валида? Вроде бы очень быстро. coderhelper.com/questions/311165/…

patridge
13 января 2010 в 16:29
6

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

iJK
20 апреля 2010 в 22:11
0

Я получил другой результат, когда использовал «ByteArrayToHexStringViaBitConverter» и «ByteArrayToHexStringViaStringBuilder». Последний оказался «правильным». Есть ли причина, по которой результат двух функций должен отличаться?

patridge
4 октября 2011 в 21:36
0

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

CodesInChaos
15 января 2013 в 08:57
0

Вы можете добавить мое решение? В моем тесте это было примерно на 25% быстрее, чем ByteArrayToHexViaByteManipulation

patridge
15 января 2013 в 18:01
2

@CodesInChaos Готово. И в моих тестах он тоже немного выиграл. Я пока не претендую на то, чтобы полностью понять ни один из основных методов, но их легко скрыть от прямого взаимодействия.

Jeppe Stig Nielsen
8 апреля 2013 в 18:03
0

Начиная с C # 4 (VS2010), наиболее естественным является String.Concat(bytes.Select(b => b.ToString("X2"))). Обратите внимание, что мы не выделяем и не копируем весь новый временный массив, как в случае Array.ConvertAll<>. Это решение также было предоставлено @AllonGuralnek в комментарии к ответу Уилла Дина. Я думаю, мы можем согласиться с тем, что сегодня люди будут использовать LINQ Select.

patridge
8 апреля 2013 в 20:37
6

Этот ответ не имеет намерения ответить на вопрос, что является «естественным» или обычным. Цель состоит в том, чтобы дать людям несколько базовых тестов производительности, поскольку, когда вам нужно выполнить это преобразование, вы, как правило, делаете их много. Если кому-то нужна чистая скорость, они просто запускают тесты с некоторыми соответствующими тестовыми данными в желаемой вычислительной среде. Затем поместите этот метод в метод расширения, где вы больше никогда не будете смотреть на его реализацию (например, bytes.ToHexStringAtLudicrousSpeed()).

Thymine
6 мая 2013 в 23:09
0

Попробуйте этот ответ: coderhelper.com/a/5919521/356218 В моих тестах производительности (где-то в этом ответе) он немного быстрее, чем ByteArrayToHexViaByteManipulation, но намного чище код

patridge
4 июня 2013 в 15:16
0

Я поместил его в среду тестирования и запустил его. Это, безусловно, достаточно быстро, чтобы люди могли использовать его самостоятельно, хотя в тестах он не стал лидером.

Maarten Bodewes
20 января 2014 в 23:20
0

@CodesInChaos У меня есть реализация "битовой скрипки", использующая единственный цикл для декодирования шестнадцатеричной строки. Хотите протестировать? Черт возьми, коды для того, чтобы избить меня, чтобы немного возиться с реализацией: P

CodesInChaos
21 июня 2014 в 17:12
2

Только что создал высокопроизводительную реализацию на основе таблицы поиска. Его безопасный вариант примерно на 30% быстрее текущего лидера на моем CPU. Небезопасные варианты еще быстрее. coderhelper.com/a/24343727/445517

patridge
31 июля 2014 в 22:42
0

@CodesInChaos Это определенно быстрее в этой глупой тестовой среде perf, которую я собрал для этого ответа. Я обновил ответ и репозиторий тестовой среды. Я добавил только безопасный вариант, так что у вас, вероятно, есть серьезная нечестивая скорость с этой небезопасной версией. Отлично сделано, сэр.

Joe Amenta
27 августа 2015 в 23:11
0

Только что понял, таблица поиска unsafe в тестовой программе производительности верна только тогда, когда BitConverter.IsLittleEndian (msdn.microsoft.com/en-us/library/…) истинен. Исходный код @CodesInChaos справляется с этим должным образом. Не большая проблема, но вот она.

patridge
28 августа 2015 в 16:04
0

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

lethek
8 августа 2016 в 00:51
0

Обнаружена ошибка в ByteArrayToHexViaLookupPerByte, вызывающая исключение IndexOutOfRangeException: массив _Lookup32 должен содержать 256 элементов, а не 255. Например. попробуйте выполнить ByteArrayToHexViaLookupPerByte(new byte[] { 255 })

Goodies
6 сентября 2016 в 12:22
0

Отличный ответ. Я использовал второй вариант в вашем списке результатов coderhelper.com/a/24343727/48700 и протестировал его с помощью веб-сокета SignalR для передачи изображений слушателям без использования каких-либо файлов. На стороне javascript: var src = 'data: image / jpeg; base64,' + hexToBase64 (binimage); $ ("# ага"). attr ("src", src);

GilesDMiddleton
10 августа 2018 в 09:44
2

@Goodies Я обнаружил, что простой Convert.ToBase64String () ОЧЕНЬ быстрый (быстрее, чем поиск по байтам (через CodesInChaos)) в моем тестировании - так что, если кому-то наплевать на шестнадцатеричный вывод, это быстро - замена линии.

metabuddy
15 марта 2019 в 11:27
0

@CodesInChaos, у вас отличная функция манипулирования байтами, спасибо!

Peter Cordes
13 апреля 2020 в 19:32
1

bin в hex можно векторизовать с помощью SIMD. Например, github.com/darealshinji/vectorclass/blob/… оболочки C ++ библиотеки векторных классов Agner Fog для встроенных функций Intel (SSE / AVX). Мой ответ на Как преобразовать двоичное целое число в шестнадцатеричную строку? находится в сборке, поэтому может быть перенесен на встроенные функции C # или встроенные функции C ++ с равными усилиями. Создание вектора из 16 или 32 байт шестнадцатеричных данных за один раз с помощью нескольких перетасовок и сдвигов может выполняться как минимум в 5 раз быстрее, чем таблица поиска байтов. @CodesInChaos

Peter Cordes
13 апреля 2020 в 19:41
0

Или с AVX512VBMI на Ice Lake, 8, 16, 32 или 64 байта шестнадцатеричных данных ASCII (от половины входной ширины) за пару тактов с парой инструкций, в частности, vpmultishiftqb, что делает параллельное извлечение битового поля. Конечно, тогда проблема заключается в форматировании шестнадцатеричных цифр с пробелами между фрагментами или '-' для UUID. Например, я играл с SIMD для libuuid (marc.info/?l=util-linux-ng&m=158526494427117&w=2) / godbolt.org/z/WB467390224< > SSSE3 примерно в 14 раз быстрее, чем оптимизированный скалярный C ++ на Skylake для UUID.