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

StackOverflow https://stackoverflow.com/questions/311165

  •  10-07-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

Или:

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.

Другие советы

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

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

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

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

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

Полученные результаты

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

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

Тестирование кода

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

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

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

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

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

Обновление (05 февраля 2012 г.)

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

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

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

Обновление (15 января 2013 г.)

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

Обновление (23 мая 2013 г.)

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

Обновление (31 июля 2014 г.)

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

Обновление (20 августа 2015 г.)

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

Существует класс под названием 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();
}

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

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

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

П'нглуи мглв'нафх Ктулху Р'льех вга'нагл фхтагн


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

Объяснение странной возни:

  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 допускает ненужные преобразования из байта и в байт.

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

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

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

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

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

Больше информации: Метод BitConverter.ToString (Byte[])

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

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

Источник: сообщение на форуме byte [] Array to Hex String (см. сообщение PZahra). Я немного изменил код, чтобы удалить префикс 0x.

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

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

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

Это ответ на редакция 4 из Очень популярный ответ Томалака (и последующие правки).

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

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

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

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

Исходный код:

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

Версия 4:

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

Пересмотр позволяет избежать String.Substring и использует StringReader вместо.Указанная причина:

Редактировать:Вы можете повысить производительность для длинных строк, используя один проходной анализатор, например:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

String.Substring

  • Худший случай:Одно быстрое распределение, одно быстрое копирование.
  • Лучший случай:Нет распределения, нет копирования.

Ручной метод

  • Худший случай:Два обычных распределения, одна обычная копия, одна быстрая копия.
  • Лучший случай:Одно обычное распределение, одна нормальная копия.

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

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

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

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

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

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

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

Да!

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

209113288F93A9AB8E474EA78D899AFDBB874355

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

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

Для «А»... «Ф» это hi = ch - 65 + 10; (это из-за 0x00000000 & 7).

Для 'a'..'f' нам нужны большие числа, поэтому мы должны вычесть 32 из версии по умолчанию, создав несколько битов. 0 используя & 0x0f.

65 это код 'A'

48 это код '0'

7 — количество букв между '9' и 'A' в таблице ASCII (...456789:;<=>?@ABCD...).

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

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

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

С#:

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

ВБ:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")

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

Give me that string:
04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939f

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

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

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

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

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

Реализация:

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

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

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

  return ret;
}

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

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

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

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

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

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

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

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

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

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

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

Хорошая и простая конверсия от разработчиков Microsoft:

public static string ByteArrayToString(byte[] ba) 
{
    // Concatenate the bytes into one long string
    return ba.Aggregate(new StringBuilder(32),
                            (sb, b) => sb.Append(b.ToString("X2"))
                            ).ToString();
}

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

public static string ByteArrayToString(byte[] ba)   
{   
   StringBuilder hex = new StringBuilder(ba.Length * 2);   

   for(int i=0; i < ga.Length; i++)       // <-- Use for loop is faster than foreach   
       hex.Append(ba[i].ToString("X2"));   // <-- ToString is faster than AppendFormat   

   return hex.ToString();   
} 

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

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

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

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

Эта версия ByteArrayToHexViaByteManipulation могла бы быть быстрее.

Из моих отчетов:

  • ByteArrayToHexViaByteManipulation3:1,68 среднего тика (более 1000 запусков), 17,5X
  • ByteArrayToHexViaByteManipulation2:1,73 среднего тика (более 1000 запусков), 16,9X
  • ByteArrayToHexViaByteManipulation:2,90 средних тиков (более 1000 запусков), 10,1X
  • БайтАррайТоХексВиаЛукупАнСдвиг: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);
    }

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

Для повышения производительности я бы выбрал раствор drphrozens. Небольшой оптимизацией для декодера может быть использование таблицы для любого символа, чтобы избавиться от & Quot; & Lt; & Lt; 4 Quot &;.

Очевидно, что два вызова метода являются дорогостоящими. Если какая-либо проверка выполняется для входных или выходных данных (например, CRC, контрольной суммы или чего-либо другого), if (b == 255)... можно пропустить, и, таким образом, метод также будет вызываться вообще.

Использование 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]));
}

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

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

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

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

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

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

Два гибридных приложения, которые объединяют две операции по клеву.

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

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

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

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

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

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

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

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

<Ол>
  • Штрафы за индексирование в строку для выполнения поиска по сравнению с массив символов важен в тесте больших файлов.
  • Штрафы за использование StringBuilder известной емкости против символа массив известного размера для создания строки еще более значим.
  • Вот код:

    using System;
    
    namespace ConversionExtensions
    {
        public static class ByteArrayExtensions
        {
            private readonly static char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    
            public static string ToHexString(this byte[] bytes)
            {
                char[] hex = new char[bytes.Length * 2];
                int index = 0;
    
                foreach (byte b in bytes)
                {
                    hex[index++] = digits[b >> 4];
                    hex[index++] = digits[b & 0x0F];
                }
    
                return new string(hex);
            }
        }
    }
    
    
    using System;
    using System.IO;
    
    namespace ConversionExtensions
    {
        public static class StringExtensions
        {
            public static byte[] ToBytes(this string hexString)
            {
                if (!string.IsNullOrEmpty(hexString) && hexString.Length % 2 != 0)
                {
                    throw new FormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");
                }
    
                hexString = hexString.ToUpperInvariant();
                byte[] data = new byte[hexString.Length / 2];
    
                for (int index = 0; index < hexString.Length; index += 2)
                {
                    int highDigitValue = hexString[index] <= '9' ? hexString[index] - '0' : hexString[index] - 'A' + 10;
                    int lowDigitValue = hexString[index + 1] <= '9' ? hexString[index + 1] - '0' : hexString[index + 1] - 'A' + 10;
    
                    if (highDigitValue < 0 || lowDigitValue < 0 || highDigitValue > 15 || lowDigitValue > 15)
                    {
                        throw new FormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");
                    }
                    else
                    {
                        byte value = (byte)((highDigitValue << 4) | (lowDigitValue & 0x0F));
                        data[index / 2] = value;
                    }
                }
    
                return data;
            }
        }
    }
    

    Ниже приведены результаты тестирования, которые я получил, когда поместил свой код в проект тестирования @ patridge на моей машине. Я также добавил тест для преобразования байтового массива из шестнадцатеричного. Тестовые прогоны, которые выполняли мой код: ByteArrayToHexViaOptimizedLookupAndShift и HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte был взят из XXXX. HexToByteArrayViaSoapHexBinary является ответом @ Mykroft.

      

    Процессор Intel Pentium III Xeon

        Cores: 4 <br/>
        Current Clock Speed: 1576 <br/>
        Max Clock Speed: 3092 <br/>
    
         <Ч>      

    Преобразование массива байтов в шестнадцатеричное строковое представление

         <Ч>      

    ByteArrayToHexViaByteManipulation2: 39 366,64 средних тиков (более 1000 прогонов), 22,4X

         

    ByteArrayToHexViaOptimizedLookupAndShift: 41 588,64 средних тиков   (более 1000 пробежек), 21,2X

         

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

         

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

         

    ByteArrayToHexViaLookupAndShift: 86 926,87 средних тиков (более 1000   работает), 10,2X

         

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

         

    ByteArrayToHexViaSoapHexBinary: 314 598,77 средних тиков (более 1000 прогонов), 2,8X      

    ByteArrayToHexStringViaStringBuilderForEachByteToString: 344 264,63   средние тики (более 1000 прогонов), 2,6X

         

    ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382 623,44   средние тики (более 1000 прогонов), 2,3X

         

    ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818,111,95   средние тики (более 1000 прогонов), 1,1X

         

    ByteArrayToHexStringViaStringConcatArrayConvertAll: в среднем 839 244,84   тики (более 1000 прогонов), 1,1X

         

    ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867,303.98   средние тики (более 1000 прогонов), 1,0X

         

    ByteArrayToHexStringViaStringJoinArrayConvertAll: в среднем 882 710,28   тики (более 1000 прогонов), 1,0X

         <Ч>

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

    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;
    }
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top