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

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

30 ответов


либо:

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.


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

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

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

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

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

результаты

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

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

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

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

  1. Добавить новый статический метод (Func<byte[], string>) to /Tests/ConvertByteArrayToHexString/Test.цезий.
  2. добавьте имя этого метода в TestCandidates возвращаемое значение в том же классе.
  3. убедитесь, что вы используете входную версию, которую хотите, предложение или текст, переключая комментарии в GenerateTestInput в этом же классе.
  4. нажмите Ф5 и дождитесь вывода (дамп HTML также генерируется в папке /bin).
static string ByteArrayToHexStringViaStringJoinArrayConvertAll(byte[] bytes) {
    return string.Join(string.Empty, Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaStringConcatArrayConvertAll(byte[] bytes) {
    return string.Concat(Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaBitConverter(byte[] bytes) {
    string hex = BitConverter.ToString(bytes);
    return hex.Replace("-", "");
}
static string ByteArrayToHexStringViaStringBuilderAggregateByteToString(byte[] bytes) {
    return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.Append(b.ToString("X2"))).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachByteToString(byte[] bytes) {
    StringBuilder hex = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes)
        hex.Append(b.ToString("X2"));
    return hex.ToString();
}
static string ByteArrayToHexStringViaStringBuilderAggregateAppendFormat(byte[] bytes) {
    return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.AppendFormat("{0:X2}", b)).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachAppendFormat(byte[] bytes) {
    StringBuilder hex = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes)
        hex.AppendFormat("{0:X2}", b);
    return hex.ToString();
}
static string ByteArrayToHexViaByteManipulation(byte[] bytes) {
    char[] c = new char[bytes.Length * 2];
    byte b;
    for (int i = 0; i < bytes.Length; i++) {
        b = ((byte)(bytes[i] >> 4));
        c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
        b = ((byte)(bytes[i] & 0xF));
        c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
    }
    return new string(c);
}
static string ByteArrayToHexViaByteManipulation2(byte[] bytes) {
    char[] c = new char[bytes.Length * 2];
    int b;
    for (int i = 0; i < bytes.Length; i++) {
        b = bytes[i] >> 4;
        c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
        b = bytes[i] & 0xF;
        c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
    }
    return new string(c);
}
static string ByteArrayToHexViaSoapHexBinary(byte[] bytes) {
    SoapHexBinary soapHexBinary = new SoapHexBinary(bytes);
    return soapHexBinary.ToString();
}
static string ByteArrayToHexViaLookupAndShift(byte[] bytes) {
    StringBuilder result = new StringBuilder(bytes.Length * 2);
    string hexAlphabet = "0123456789ABCDEF";
    foreach (byte b in bytes) {
        result.Append(hexAlphabet[(int)(b >> 4)]);
        result.Append(hexAlphabet[(int)(b & 0xF)]);
    }
    return result.ToString();
}
static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_Lookup32, GCHandleType.Pinned).AddrOfPinnedObject();
static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes) {
    var lookupP = _lookup32UnsafeP;
    var result = new string((char)0, bytes.Length * 2);
    fixed (byte* bytesP = bytes)
    fixed (char* resultP = result) {
        uint* resultP2 = (uint*)resultP;
        for (int i = 0; i < bytes.Length; i++) {
            resultP2[i] = lookupP[bytesP[i]];
        }
    }
    return result;
}
static uint[] _Lookup32 = Enumerable.Range(0, 255).Select(i => {
    string s = i.ToString("X2");
    return ((uint)s[0]) + ((uint)s[1] << 16);
}).ToArray();
static string ByteArrayToHexViaLookupPerByte(byte[] bytes) {
    var result = new char[bytes.Length * 2];
    for (int i = 0; i < bytes.Length; i++)
    {
        var val = _Lookup32[bytes[i]];
        result[2*i] = (char)val;
        result[2*i + 1] = (char) (val >> 16);
    }
    return new string(result);
}
static string ByteArrayToHexViaLookup(byte[] bytes) {
    string[] hexStringTable = new string[] {
        "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F",
        "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F",
        "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F",
        "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F",
        "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F",
        "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F",
        "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F",
        "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F",
        "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F",
        "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F",
        "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF",
        "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF",
        "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF",
        "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF",
        "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF",
        "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF",
    };
    StringBuilder result = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes) {
        result.Append(hexStringTable[b]);
    }
    return result.ToString();
}

обновление (2010-01-13)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

добавил airbreather это оптимизация и 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);
}

рН'nglui mglw все'nafh Ктулху Р'lyeh wgah'nagl fhtagn


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

объяснение странное скрипка:

  1. bytes[i] >> 4 извлекает высокий укус байта
    bytes[i] & 0xF извлекает низкий укус байта
  2. b - 10
    is < 0 для значений b < 10, который станет десятичной цифрой
    is >= 0 для значений b > 10, которое станет письмом от A to 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) to 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 как верхняя граница loop позволяет дрожанию устранить проверки границ на 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")));

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


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


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

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

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

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


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

  • таблица кодировщика 512 байт или 1024 байта (дважды размер, если верхний и нижний регистр необходимо)
  • таблица дешифратора 256 байт или 64 KiB (либо один поиск символов или двойной char look-up)

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

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

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

предупреждение: этот ответ может устареть если a 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 вместо. Причина этого:

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

хорошо, глядя на ссылка код 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;
    }

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

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

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

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

вам действительно нужен что шагом в шагах двух параллельно 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

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

ручной метод

  • худший случай: два нормальных распределения, одна нормальная копия, одна быстрая копия.
  • Best-case: одно нормальное распределение, одна обычная копия.

вывод? если вы хотите использовать 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).

Для 'A'..'F' это 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:

C#:

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

В. Б.:

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

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

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 бы есть проверить на правильность. Обратите внимание, что они равны.

реализация:

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

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

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

  return ret;
}

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

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


безопасная версии:

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

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

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

        const string alphabet = @"0123456789ABCDEF";

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

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

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

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

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


обратная функция для кода Waleed Eissa (Hex String to Byte Array):

    public static byte[] HexToBytes(this string hexString)        
    {
        byte[] b = new byte[hexString.Length / 2];            
        char c;
        for (int i = 0; i < hexString.Length / 2; i++)
        {
            c = hexString[i * 2];
            b[i] = (byte)((c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57)) << 4);
            c = hexString[i * 2 + 1];
            b[i] += (byte)(c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57));
        }

        return b;
    }

функция Waleed Eissa с поддержкой нижнего регистра:

    public static string BytesToHex(this byte[] barray, bool toLowerCase = true)
    {
        byte addByte = 0x37;
        if (toLowerCase) addByte = 0x57;
        char[] c = new char[barray.Length * 2];
        byte b;
        for (int i = 0; i < barray.Length; ++i)
        {
            b = ((byte)(barray[i] >> 4));
            c[i * 2] = (char)(b > 9 ? b + addByte : b + 0x30);
            b = ((byte)(barray[i] & 0xF));
            c[i * 2 + 1] = (char)(b > 9 ? b + addByte : b + 0x30);
        }

        return new string(c);
    }

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

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

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


от разработчиков 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);
  }

Я не получил код, который вы предложили для работы, Olipro. hex[i] + hex[i+1] видимо возвратил int.

Я, однако, имел некоторый успех, взяв некоторые подсказки из кода Waleeds и забив это вместе. Это уродливо, как ад, но, похоже, работает и работает в 1/3 времени по сравнению с другими в соответствии с моими тестами (используя механизм тестирования patridges). В зависимости от размера входного сигнала. Переключение вокруг ?: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,5 X
  • ByteArrayToHexViaByteManipulation2: 1,73 средних тиков (более 1000 пробегов), 16,9 X
  • ByteArrayToHexViaByteManipulation: 2,90 средних тиков (более 1000 пробегов), 10,1 X
  • ByteArrayToHexViaLookupAndShift: 3,22 средних тиков (более 1000 запусков), 9,1 х
  • ...

    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.


и для вставки в строку 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

два mashups, который складывает две операции грызть в один.

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

public static string ByteArrayToString2(byte[] ba)
{
    char[] c = new char[ba.Length * 2];
    for( int i = 0; i < ba.Length * 2; ++i)
    {
        byte b = (byte)((ba[i>>1] >> 4*((i&1)^1)) & 0xF);
        c[i] = (char)(55 + b + (((b-10)>>31)&-7));
    }
    return new string( c );
}

декадентская версия linq-with-bit-hacking:

public static string ByteArrayToString(byte[] ba)
{
    return string.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) );
}

и обратное:

public static byte[] HexStringToByteArray( string s )
{
    byte[] ab = new byte[s.Length>>1];
    for( int i = 0; i < s.Length; i++ )
    {
        int b = s[i];
        b = (b - '0') + ((('9' - b)>>31)&-7);
        ab[i>>1] |= (byte)(b << 4*((i&1)^1));
    }
    return ab;
}

другой способ-использовать 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 ] = '';
        return new string(c);
}

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

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

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

Tohexstringchararraywithcharraylookup: 41,589.69 средних тиков (более 1000 пробегов), 1,5 X ToHexStringCharArrayWithStringLookup: 50,764.06 средние тики (более 1000 пробегов), 1.2 X ToHexStringStringBuilderWithChararraylookup: 62,812.87 средних тиков (более 1000 пробегов), 1.0 X

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

  1. в штрафы за индексирование в строку для выполнения поиска против a массив 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,4 X

ByteArrayToHexViaOptimizedLookupandshift: 41,588.64 средние тики (более 1000 пробегов), 21.2 X

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

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

ByteArrayToHexViaLookupAndShift: 86,926.87 средние тики (более Одна тысяча runs), 10.2 X

ByteArrayToHexStringViaBitConverter: 139,353.73 средний тики (более 1000 пробегов), 6.3 X

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

ByteArrayToHexStringViaStringBuilderforeachbytetostring: 344,264.63 средние тики (более 1000 пробегов), 2.6 X

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

ByteArrayToHexStringViaStringBuilderforeachappendformat: 818,111.95 средние тики (более 1000 пробегов), 1.1 X

ByteArrayToHexStringViaStringConcatarrayconvertall: 839,244.84 средний тики (более 1000 пробегов), 1.1 X

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

ByteArrayToHexStringViaStringJoinarrayconvertall: 882,710.28 средний тики (более 1000 пробегов), 1.0 X



другая быстрая функция...

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

для производительности я бы пошел с решением drphrozens. Крошечная оптимизация для декодера может заключаться в использовании таблицы для любого символа, чтобы избавиться от "

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

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