Як перетворити байтовий масив у шістнадцятковий рядок і навпаки?


1371

Як можна перетворити байтовий масив у шістнадцятковий рядок і навпаки?


8
Прийнята нижче відповідь, як видається, виділяє жахливу кількість рядків у рядку для перетворення байтів. Мені цікаво, як це впливає на результативність
Вім Коен

9
Клас SoapHexBinary робить саме те, що ви хочете, я думаю.
Mykroft

Мені здається, що задавати 2 запитання в 1 дописі не зовсім стандартно.
SandRock

Відповіді:


1353

Або:

public static string ByteArrayToString(byte[] ba)
{
  StringBuilder hex = new StringBuilder(ba.Length * 2);
  foreach (byte b in ba)
    hex.AppendFormat("{0:x2}", b);
  return hex.ToString();
}

або:

public static string ByteArrayToString(byte[] ba)
{
  return BitConverter.ToString(ba).Replace("-","");
}

Є ще більше варіантів цього, наприклад, тут .

Зворотне перетворення піде так:

public static byte[] StringToByteArray(String hex)
{
  int NumberChars = hex.Length;
  byte[] bytes = new byte[NumberChars / 2];
  for (int i = 0; i < NumberChars; i += 2)
    bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
  return bytes;
}

Використання Substring- найкращий варіант у поєднанні з Convert.ToByte. Дивіться цю відповідь для отримання додаткової інформації. Якщо вам потрібна краща продуктивність, ви повинні уникати, Convert.ToByteперш ніж ви зможете скинути її SubString.


24
Ви використовуєте SubString. Хіба цей цикл не виділяє жахливу кількість об'єктів рядка?
Вім Коен

30
Чесно кажучи - до тих пір, поки це не призведе до різкого зменшення продуктивності, я схиляюся до цього ігнорувати і довіряти Runtime та GC, щоб піклуватися про це.
Томалак

87
Оскільки байт - це два nibbles, будь-яка шістнадцятка рядків, що дійсно представляє байтовий масив, повинна мати парне число символів. 0 не слід додавати нікуди - для додавання можна було б зробити припущення про недійсні дані, які є потенційно небезпечними. Якщо що-небудь, метод StringToByteArray повинен викинути FormatException, якщо шістнадцятковий рядок містить непарну кількість символів.
Девід Бойке

7
@ 00jt Ви повинні зробити припущення, що F == 0F. Або це те саме, що 0F, або вхід був вирізаний, і F насправді є початком чогось, що ви не отримали. Ви можете зробити ці припущення саме вашим контекстом, але я вважаю, що функція загального призначення повинна відхиляти непарні символи як недійсні, а не робити це припущення для виклику коду.
Девід Бойке

11
@DavidBoike Питання не мало нічого спільного з "як обробити можливі відрізані значення потоку". Це стосується рядка. Рядок myValue = 10.ToString ("X"); myValue - це "A", а не "0A". Тепер перечитайте цей рядок назад в байти, ой, ви його зламали.
00jt

488

Аналіз ефективності

Примітка: новий керівник станом на 20.08.2015.

Я провів кожен з різних методів перетворення за допомогою певного Stopwatchтестування грубої продуктивності, пробігу з випадковим реченням (n = 61, 1000 ітерацій) та запуску з текстом проекту Гутенбурга (n = 1,238,957, 150 ітерацій). Ось результати, приблизно від найшвидшого до найповільнішого. Всі вимірювання проводяться в тиках ( 10000 тиків = 1 мс ), і всі відносні замітки порівнюються з [найповільнішим] StringBuilderвиконанням. Про використаний код див. Нижче або тест-рамковий репо, де я зараз підтримую код для цього.

Відмова від відповідальності

ПОПЕРЕДЖЕННЯ: Не покладайтеся на цю статистику на щось конкретне; вони є просто типовим циклом вибіркових даних. Якщо вам справді потрібні найвищі показники, будь ласка, протестуйте ці методи в середовищі, що відповідає вашим виробничим потребам, з даними, що представляють, що ви будете використовувати.

Результати

Таблиці пошуку взяли на себе перевагу над маніпулюванням байтами. В основному, існує певна форма попереднього обчислення того, що будь-який заданий nibble або byte буде в шістнадцятковій. Потім, переглядаючи дані, ви просто шукаєте наступну частину, щоб побачити, якою вона буде. Потім це значення певним чином додається до отриманого рядка. Тривалий час маніпуляції з байтом, що потенційно важче було прочитати деякими розробниками, був найефективнішим підходом.

Ваша найкраща ставка все ще буде знаходити деякі репрезентативні дані та випробовувати їх у виробничих умовах. Якщо у вас є різні обмеження пам'яті, ви можете віддати перевагу методу з меншим розміщенням розподілу, який був би швидшим, але витратив більше пам'яті.

Код тестування

Не соромтеся грати з тестовим кодом, який я використав. Тут включена версія, але сміливо клонуйте репо і додайте свої власні методи. Будь ласка, надішліть запит на витяг, якщо ви виявите щось цікаве або хочете допомогти вдосконалити тестові рамки, які він використовує.

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

Оновлення (13.01.2010)

Додана відповідь Уеліда до аналізу. Досить швидко.

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

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

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

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

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

Додано відповідь Mykroft SoapHexBinaryдо аналізу, який посів третє місце.

Оновлення (15.01.2013)

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

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

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

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

Додано нову відповідь пошуку на байті @ CodesInChaos. Схоже, він взяв на себе ініціативу як на тести речень, так і на повнотекстові тести.

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

До репо-відповіді цієї відповіді додано оптимізацію та unsafeваріант повітряного дихача . Якщо ви хочете грати в небезпечну гру, ви можете отримати величезні результати від будь-якого з попередніх головних переможців як на коротких рядках, так і на великих текстах.


Чи хотіли б ви перевірити код з відповіді Уеліда? Здається, це дуже швидко. stackoverflow.com/questions/311165/…
Крістіан Діаконеску

5
Незважаючи на те, що ви можете зробити код доступним для того, щоб зробити те, що ви просили самостійно, я оновив код тестування, щоб включити відповідь Уеліда. Всі бурчання вбік, це набагато швидше.
патрон

2
@CodesInChaos Готово. І це теж виграло в моїх тестах. Я не претендую на те, що повністю розумію жоден із найкращих методів, але їх легко приховати від прямої взаємодії.
патрон

6
Ця відповідь не має наміру відповідати на питання про те, що є "природним" чи звичним. Мета полягає в тому, щоб дати людям основні орієнтири ефективності, оскільки, коли вам потрібно зробити ці перетворення, ви, як правило, робите їх багато. Якщо комусь потрібна швидка швидкість, вони просто запускають орієнтири з відповідними тестовими даними в бажаному обчислювальному середовищі. Потім витягніть цей метод у метод розширення, де ви більше ніколи не переглянете його реалізацію (наприклад, bytes.ToHexStringAtLudicrousSpeed()).
патрон

2
Щойно створено реалізацію на основі таблиці високої продуктивності. Його безпечний варіант приблизно на 30% швидший, ніж нинішній лідер мого процесора. Небезпечні варіанти ще швидші. stackoverflow.com/a/24343727/445517
CodesInChaos

244

Існує клас під назвою SoapHexBinary, який робить саме те, що ви хочете.

using System.Runtime.Remoting.Metadata.W3cXsd2001;

public static byte[] GetStringToBytes(string value)
{
    SoapHexBinary shb = SoapHexBinary.Parse(value);
    return shb.Value;
}

public static string GetBytesToString(byte[] value)
{
    SoapHexBinary shb = new SoapHexBinary(value);
    return shb.ToString();
}

35
SoapHexBinary доступний у .NET 1.0 і знаходиться в mscorlib. Незважаючи на це смішний простір імен, він робить саме те, що задав питання.
Хитрий Грифон

4
Чудова знахідка! Зауважте, що вам потрібно буде прокладати непарні рядки з провідним 0 для GetStringToBytes, як і інше рішення.
Картер Медлін

Ви бачили думку про реалізацію? Прийнята відповідь має кращий ІМХО.
mfloryan

6
Цікаво ознайомитись із реалізацією Mono тут: github.com/mono/mono/blob/master/mcs/class/corlib/…
Джеремі

1
SoapHexBinary не підтримується в .NET Core / .NET Standard ...
juFo

141

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

Це також досить швидко.

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

Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn


Відмовтеся від усієї надії, ви, хто сюди входить

Пояснення дивного біда:

  1. bytes[i] >> 4витягує високу плетіння байта,
    bytes[i] & 0xFвитягує нижню клітку байта
  2. b - 10
    є < 0для значень b < 10, які стануть десятковою цифрою,
    це >= 0для значень b > 10, які стануть літерою від Aдо F.
  3. Використання i >> 3132-бітового цілого цілого числа витягує знак завдяки розширення знаку. Це буде -1за i < 0і 0для i >= 0.
  4. Поєднуючи 2) і 3), показує, що (b-10)>>31буде 0для літер і -1для цифр.
  5. Дивлячись на регістр букв, остання сума викликає значення 0і bзнаходиться в діапазоні від 10 до 15. Ми хочемо відобразити її на A(65) - F(70), що означає додавання 55 ( 'A'-10).
  6. Розглядаючи випадок для цифр, ми хочемо адаптувати останню підсумку, щоб вона відображалася bвід діапазону від 0 до 9 до діапазону 0(48) до 9(57). Це означає, що йому потрібно стати -7 ( '0' - 55).
    Тепер ми можемо просто помножити на 7. Але оскільки -1 представлено усіма бітами, що дорівнюють 1, ми можемо натомість використовувати & -7з (0 & -7) == 0і (-1 & -7) == -7.

Деякі додаткові міркування:

  • Я не використовував другу змінну циклу для індексації c, оскільки вимірювання показує, що обчислити її iдешевше.
  • Використання саме i < bytes.Lengthяк верхньої межі циклу дозволяє JITter усунути перевірку меж bytes[i], тому я вибрав такий варіант.
  • Створення bint дозволяє робити непотрібні перетворення з байта в байт.

10
А hex stringдо byte[] array?
AaA

15
+1 за правильне посилання на джерело після виклику цього шматочка чорної магії. Всі вітають Ктулху.
Едвард

4
Що про рядок до байта []?
Syaiful Nizam Yahya

9
Приємно! Для тих, хто потребує виведення малих літер, вираз очевидно змінюється на87 + b + (((b-10)>>31)&-39)
eXavier

2
@AaA Ви сказали " byte[] array", що буквально означає масив байтових масивів, або byte[][]. Я просто забавлявся.
CoolOppo

97

Якщо ви хочете більшої гнучкості BitConverter, але не хочете тих чітких циклів стилю 1990-х, тоді ви можете зробити:

String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));

Або якщо ви використовуєте .NET 4.0:

String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));

(Остання з коментаря до оригінальної публікації.)


21
Ще коротше: String.Concat (Array.ConvertAll (байти, x => x.ToString ("X2"))
Нестор

14
Ще коротше: String.Concat (bytes.Select (b => b.ToString ("X2")) [.NET4]
Аллон Гуралнек

14
Відповідає лише на половину запитання.
Хитрий Грифон

1
Для чого потрібен другий. Net 4? String.Concat знаходиться у .Net 2.0.
Поліфун

2
ці петлі "стилю 90-х", як правило, швидші, але достатньо незначні, що це не має значення в більшості контекстів. Ще варто згадати, хоча
Austin_Anderson

69

Ще один підхід на основі таблиці пошуку. Цей використовує лише одну таблицю пошуку для кожного байту, а не таблицю пошуку на nibble.

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

Чому створення таблиці пошуку в небезпечній версії підміняє мітки попередньо обчисленого байта? Я думав, що ендіантність лише змінила впорядкованість сутностей, які були сформовані з кількох байтів.
Райф Атеф

@RaifAtef Тут важливим є не порядок укусів. Але порядок 16 бітових слів у цілому цілі 32 біт. Але я розглядаю можливість переписати його, щоб той самий код міг працювати незалежно від витривалості.
CodesInChaos

Перечитавши код, я думаю, що ви це зробили, тому що, коли ви передаєте char * пізніше uint * і призначите його (при генерації шістнадцяткових символів), час виконання / CPU переверне байти (оскільки uint не обробляється так само, як 2 окремі 16-бітні символи), тому ви попередньо гортайте їх для компенсації. Чи правий я ? Ендіанство заплутано :-).
Райф Атеф

4
Це просто відповідь на половину питання ... Як щодо шестигранної рядки до байтів?
Нарвалекс

3
@CodesInChaos Цікаво, чи Spanможна зараз використовувати замість unsafe??
Конрад

64

Ви можете використовувати метод BitConverter.ToString:

byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256}
Console.WriteLine( BitConverter.ToString(bytes));

Вихід:

00-01-02-04-08-10-20-40-80-ФФ

Додаткова інформація: метод BitConverter.ToString (байт [])


14
Відповідає лише на половину запитання.
Хитрий Грифон

3
Де друга частина відповіді?
Саван

56

Я щойно стикався з цією ж проблемою сьогодні, і мені траплявся цей код:

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

Джерело: byte post forum [] Array to Hex String (див. Пост PZahra). Я трохи змінив код, щоб видалити префікс 0x.

Я зробив кілька тестів на працездатність коду, і це було майже у вісім разів швидше, ніж використання BitConverter.ToString () (найшвидший відповідно до публікації патронта).


не кажучи вже про те, що для цього використовується найменше пам'яті. Жодних проміжних рядків не було створено.
Чочос

8
Відповідає лише на половину запитання.
Хитрий Грифон

Це чудово, оскільки він працює в основному на будь-якій версії NET, включаючи NETMF. Переможець!
Jonesome Reinstate Monica

1
Прийнята відповідь надає 2 відмінні методи HexToByteArray, які представляють другу половину питання. Рішення Waleed відповідає на поточне питання, як це зробити, не створюючи величезну кількість рядків у процесі.
Брендтен Ейкштедт

Чи копіює та перерозподіляє новий рядок (c) чи достатньо розумний, щоб знати, коли він може просто обернути знак []?
jjxtra

19

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

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

tl; dr: Просто використовуйте, Convert.ToByteі String.Substringякщо ви поспішаєте ("Оригінальний код" нижче), це найкраща комбінація, якщо ви не хочете повторно реалізовувати Convert.ToByte. Використовуйте щось більш досконале (див. Інші відповіді), яке не використовується, Convert.ToByteякщо вам потрібна продуктивність. Є НЕ що - небудь ще інше використання , ніж String.Substringв поєднанні з Convert.ToByte, якщо хто - то має що - то цікаве , щоб сказати про це в коментарях цієї відповіді.

попередження: Ця відповідь може застаріти, якщо в рамках Convert.ToByte(char[], Int32)буде здійснено перевантаження. Це навряд чи станеться незабаром.

Як правило, я не дуже люблю говорити "не оптимізувати передчасно", тому що ніхто не знає, коли це "передчасний". Єдине, що ви повинні враховувати, вирішуючи, оптимізувати чи ні, це: "Чи є у мене час і ресурси для належного дослідження підходів до оптимізації?". Якщо ви цього не зробите, тоді ще занадто рано, чекайте, поки ваш проект стане більш зрілим або до того, як вам буде потрібна ефективність (якщо є реальна потреба, тоді ви заробіть час). Тим часом зробіть найпростішу річ, яка могла б працювати замість цього.

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

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

Версія 4:

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

Перегляд уникає String.Substringта використовує StringReaderнатомість. Дана причина:

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

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

Однак він виділяє нову рядок, але тоді вам потрібно виділити один, щоб передати його в Convert.ToByteбудь-якому випадку. Крім того, рішення, що надається в редакції, виділяє ще один об'єкт на кожній ітерації (двочастовий масив); Ви можете сміливо поставити це виділення поза циклом і повторно використовувати масив, щоб уникнути цього.

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

Кожен шістнадцятковий numeralявляє собою один октет, використовуючи дві цифри (символи).

Але тоді, навіщо дзвонити StringReader.Readдвічі? Просто зателефонуйте на друге його перевантаження і попросіть прочитати відразу два символи у двоканальному масиві; і зменшити кількість дзвінків на два.

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

Те, що вам лишилося, - це рідер зчитування рядків, єдине додане "значення" - це паралельний індекс (внутрішній _pos), який ви могли б заявити про себе (як, jнаприклад), змінну зайвої довжини (внутрішню _length) та надлишкову посилання на вхід рядок (внутрішній _s) Іншими словами, марно.

Якщо вам цікаво, як Read«читається», просто подивіться на код , все, що він робить, - це дзвінок String.CopyToу рядок введення. Решта - це просто ведення бухгалтерського обліку для підтримки цінностей, які нам не потрібні.

Отже, вийміть уже зчитувач рядків і зателефонуйте CopyToсобі; це простіше, зрозуміліше та ефективніше.

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

Вам справді потрібен jіндекс, який збільшується на кроки по два паралельно i? Звичайно, ні, просто помножте iна два (що компілятор повинен мати можливість оптимізувати до додавання).

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

Як виглядає рішення зараз? Точно так, як це було на початку, лише замість того, String.Substringщоб використовувати для розподілу рядка і копіювати в нього дані, ви використовуєте посередницький масив, до якого ви копіюєте шістнадцяткові цифри, а потім виділяєте рядок і копіюєте дані знову з масив і в рядок (коли ви передаєте його в конструктор рядків). Друга копія може бути оптимізована, якщо рядок вже знаходиться у пулі інтернів, але тоді String.Substringтакож у цьому випадку ви зможете уникнути її.

Насправді, якщо ви String.Substringзнову подивитеся , ви побачите, що він використовує деякі внутрішні знання низького рівня про те, як побудовані рядки для розподілу рядка швидше, ніж ви могли це зробити, і він вказує той самий код, який використовується CopyToбезпосередньо там, щоб уникнути виклик накладні.

String.Substring

  • Найгірший варіант: одне швидке виділення, одна швидка копія.
  • Найкращий випадок: немає виділення, жодної копії.

Ручний метод

  • Найгірший: два звичайних виділення, одна нормальна копія, одна швидка копія.
  • Кращий випадок: одне звичайне виділення, одна нормальна копія.

Висновок? Якщо ви хочете використовуватиConvert.ToByte(String, Int32) (оскільки ви не хочете самостійно реалізовувати цю функціональність), мабуть, не існує способу перемогти String.Substring; все, що ви робите, - бігати по колах, заново вигадуючи колесо (тільки з неоптимальними матеріалами).

Зауважте, що використання Convert.ToByteта String.Substringє абсолютно вірним вибором, якщо вам не потрібні надзвичайні показники. Пам’ятайте: вибирайте альтернативу лише тоді, коли у вас є час та ресурси, щоб дослідити, як вона працює належним чином.

Якби це було, то Convert.ToByte(char[], Int32), звичайно , все було б інакше (можна було б зробити те, що я описав вище, і повністю уникнути String).

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

Відмова: Я не декомпілював останню версію фреймворку, щоб перевірити, чи є опорне джерело оновленим, я припускаю, що воно є.

Тепер все це звучить добре і логічно, сподіваюся, навіть очевидно, якщо вам вдалося пройти так далеко. Але це правда?

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

Так!

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

209113288F93A9AB8E474EA78D899AFDBB874355

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


помилка: {"Не вдалося знайти впізнавані цифри."}
Priya Jagtap

17

Доповнення до відповіді від @CodesInChaos (зворотний метод)

public static byte[] HexToByteUsingByteManipulation(string s)
{
    byte[] bytes = new byte[s.Length / 2];
    for (int i = 0; i < bytes.Length; i++)
    {
        int hi = s[i*2] - 65;
        hi = hi + 10 + ((hi >> 31) & 7);

        int lo = s[i*2 + 1] - 65;
        lo = lo + 10 + ((lo >> 31) & 7) & 0x0f;

        bytes[i] = (byte) (lo | hi << 4);
    }
    return bytes;
}

Пояснення:

& 0x0f є підтримка також малих букв

hi = hi + 10 + ((hi >> 31) & 7); те саме, що:

hi = ch-65 + 10 + (((ch-65) >> 31) & 7);

Для '0' .. '9' це те саме, hi = ch - 65 + 10 + 7;що є hi = ch - 48(це через 0xffffffff & 7).

Для 'A' .. 'F' це hi = ch - 65 + 10;(це через 0x00000000 & 7).

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

65 - код для 'A'

48 - код для '0'

7 - кількість літер між '9'та 'A'у таблиці ASCII ( ...456789:;<=>?@ABCD...).


16

Цю проблему можна було також вирішити за допомогою таблиці пошуку. Для цього знадобиться невелика кількість статичної пам’яті і для кодера, і для декодера. Однак цей метод буде швидким:

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

Моє рішення використовує 1024 байти для таблиці кодування та 256 байт для декодування.

Розшифровка

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

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

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

Кодування

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

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

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

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

Порівняння

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

* це рішення

Примітка

Під час декодування може виникнути IOException та IndexOutOfRangeException (якщо символ має занадто високе значення> 256). Методи де-кодування потоків чи масивів повинні бути впроваджені, це лише доказ концепції.


2
Використання пам'яті в 256 байт незначно, коли ви запускаєте код у CLR.
долмен

9

Це чудовий пост. Мені подобається рішення Уеліда. Я не запускав це через тест патронта, але це здається досить швидким. Також мені знадобився зворотний процес, перетворивши шістнадцяткову рядок у байтовий масив, тому я записав це як обернення рішення Waleed. Не впевнений, чи швидше це, ніж оригінальне рішення Томалака. Знову ж таки, я не запустив зворотний процес через тест патронта.

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

Цей код передбачає, що шістнадцятковий рядок використовує верхній регістр альфа-знаків, і підірве, якщо шістнадцятковий рядок використовує нижню величину альфа Можливо, хочете зробити перетворення у верхньому регістрі на вхідному рядку, щоб бути безпечним.
Марк Новаковський

Це проникливе спостереження Марк. Код був написаний для зворотного рішення Валіда. Виклик ToUpper дещо уповільнить алгоритм, але дозволить йому обробляти алфавітні символи нижнього регістру.
Кріс Ф

3
Convert.ToByte (topChar + bottomChar) можна записати як (байт) (topChar + bottomChar)
Амір Резай

Для вирішення обох випадків без великого штрафного виконання,hexString[i] &= ~0x20;
Бен Войгт

9

Навіщо робити це складним? У Visual Studio 2008 це просто:

C #:

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

VB:

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

2
Причина - продуктивність, коли вам потрібно високоефективне рішення. :)
Рікі

7

Не купувати відповіді на багато відповідей тут, але я знайшов досить оптимальну (~ 4,5 рази краще, ніж прийнято), просту реалізацію шестнадцяткового рядкового аналізатора. По-перше, вихід з моїх тестів (перша партія - це моя реалізація):

Give me that string:
04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939f

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

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

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

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

Рядки base64 та "BitConverter'd" є для перевірки правильності. Зауважте, що вони рівні.

Впровадження:

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

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

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

  return ret;
}

Я спробував деякі речі unsafeі перемістив (явно зайву) ifпослідовність передачі символів на інший метод, але це було найшвидшим.

(Я визнаю, що це відповідає половині запитання. Я відчував, що перетворення string-> byte [] недостатньо представлене, тоді як кут рядка byte [] -> здається добре висвітленим. Таким чином, ця відповідь.)


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

5

Безпечні версії:

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

        const string hexAlphabet = @"0123456789ABCDEF";

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

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

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

Небезпечні версії Для тих, хто вважає за краще продуктивність і не боїться небезпеки. Близько на 35% швидший ToHex і на 10% швидший FromHex.

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

        const string alphabet = @"0123456789ABCDEF";

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

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

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

BTW Для тестового тестування ініціалізації алфавіту кожного разу, коли функція перетворення, що викликається, неправильна, алфавіт повинен бути const (для рядка) або статичним читанням лише для char []). Тоді перетворення байта [] на основі алфавіту в рядок стає настільки ж швидким, як і версії маніпулювання байтом.

І звичайно тест повинен бути складений у Release (з оптимізацією) та з вимкненою опцією "Suppress JIT optimization" (те саме для "Enable Just My Code", якщо код повинен бути налагодженим).


5

Зворотна функція для коду 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);
    }

4

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

public static class ByteExtensions
{
    public static string ToHexString(this byte[] ba)
    {
        StringBuilder hex = new StringBuilder(ba.Length * 2);

        foreach (byte b in ba)
        {
            hex.AppendFormat("{0:x2}", b);
        }
        return hex.ToString();
    }
}

тощо. Використовуйте будь- яке з трьох рішень Tomalak (останнє - метод розширення на рядку).


Ви, ймовірно, повинні протестувати код, перш ніж запропонувати його для такого питання.
jww

3

Гарне, просте перетворення від розробників Microsoft:

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

Незважаючи на те, що вище чисте і компактне, наркоманки будуть кричати про це за допомогою перелічників. Ви можете досягти пікової продуктивності за допомогою покращеної версії оригінальної відповіді Томалака :

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

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

   return hex.ToString();   
} 

Це найшвидший з усіх процедур, які я бачив, розміщених тут поки що. Не просто прийміть моє слово за це ... тестуйте працездатність кожної програми та перевіряйте її код CIL для себе.


2
Ітератор не є основною проблемою цього коду. Ви повинні орієнтир b.ToSting("X2").
долмен

2

А для вставки в рядок SQL (якщо ви не використовуєте командні параметри):

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

якщо Source == nullабо у Source.Length == 0нас є проблема , сер!
Андрій Красуцький

2

Що стосується швидкості, то, здається, це краще за все тут:

  public static string ToHexString(byte[] data) {
    byte b;
    int i, j, k;
    int l = data.Length;
    char[] r = new char[l * 2];
    for (i = 0, j = 0; i < l; ++i) {
      b = data[i];
      k = b >> 4;
      r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
      k = b & 15;
      r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
    }
    return new string(r);
  }

2

Я не отримав код, який ти запропонував працювати, Оліпро. hex[i] + hex[i+1]мабуть, повернув int.

Але я мав певний успіх, взявши підказки з коду Уеліда і забивши це разом. Це некрасиво, як пекло, але, здається, працює і працює в 1/3 часу порівняно з іншими згідно з моїми тестами (використовуючи механізм тестування патриджетів). Залежно від розміру вводу. Перемикання навколо?: S для відмежування 0-9 спочатку, ймовірно, дасть дещо швидший результат, оскільки число більше, ніж букв.

public static byte[] StringToByteArray2(string hex)
{
    byte[] bytes = new byte[hex.Length/2];
    int bl = bytes.Length;
    for (int i = 0; i < bl; ++i)
    {
        bytes[i] = (byte)((hex[2 * i] > 'F' ? hex[2 * i] - 0x57 : hex[2 * i] > '9' ? hex[2 * i] - 0x37 : hex[2 * i] - 0x30) << 4);
        bytes[i] |= (byte)(hex[2 * i + 1] > 'F' ? hex[2 * i + 1] - 0x57 : hex[2 * i + 1] > '9' ? hex[2 * i + 1] - 0x37 : hex[2 * i + 1] - 0x30);
    }
    return bytes;
}

2

Ця версія маніпуляції ByteArrayToHexViaByte може бути швидшою.

З моїх звітів:

  • ByteArrayToHexViaByteManipulation3: 1,68 середніх тиків (понад 1000 пробігів), 17,5X
  • ByteArrayToHexViaByteManipulation2: 1,73 середніх тиків (понад 1000 запусків), 16,9X
  • ByteArrayToHexViaByteManipulation: 2,90 середніх тиків (понад 1000 пробігів), 10,1X
  • ByteArrayToHexViaLookupAndShift: 3,22 середніх кліща (понад 1000 пробігів), 9,1X
  • ...

    static private readonly char[] hexAlphabet = new char[]
        {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    static string ByteArrayToHexViaByteManipulation3(byte[] bytes)
    {
        char[] c = new char[bytes.Length * 2];
        byte b;
        for (int i = 0; i < bytes.Length; i++)
        {
            b = ((byte)(bytes[i] >> 4));
            c[i * 2] = hexAlphabet[b];
            b = ((byte)(bytes[i] & 0xF));
            c[i * 2 + 1] = hexAlphabet[b];
        }
        return new string(c);
    }

І я думаю, що це оптимізація:

    static private readonly char[] hexAlphabet = new char[]
        {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    static string ByteArrayToHexViaByteManipulation4(byte[] bytes)
    {
        char[] c = new char[bytes.Length * 2];
        for (int i = 0, ptr = 0; i < bytes.Length; i++, ptr += 2)
        {
            byte b = bytes[i];
            c[ptr] = hexAlphabet[b >> 4];
            c[ptr + 1] = hexAlphabet[b & 0xF];
        }
        return new string(c);
    }

2

Я вступлю в цей конкурс на трохи біг, оскільки у мене є відповідь, яка також використовує біт-фіддінг для розшифровки шістнадцяткових знаків. Зауважте, що використання масивів символів може бути ще швидшим, оскільки StringBuilderметоди виклику також потребують часу.

public static String ToHex (byte[] data)
{
    int dataLength = data.Length;
    // pre-create the stringbuilder using the length of the data * 2, precisely enough
    StringBuilder sb = new StringBuilder (dataLength * 2);
    for (int i = 0; i < dataLength; i++) {
        int b = data [i];

        // check using calculation over bits to see if first tuple is a letter
        // isLetter is zero if it is a digit, 1 if it is a letter
        int isLetter = (b >> 7) & ((b >> 6) | (b >> 5)) & 1;

        // calculate the code using a multiplication to make up the difference between
        // a digit character and an alphanumerical character
        int code = '0' + ((b >> 4) & 0xF) + isLetter * ('A' - '9' - 1);
        // now append the result, after casting the code point to a character
        sb.Append ((Char)code);

        // do the same with the lower (less significant) tuple
        isLetter = (b >> 3) & ((b >> 2) | (b >> 1)) & 1;
        code = '0' + (b & 0xF) + isLetter * ('A' - '9' - 1);
        sb.Append ((Char)code);
    }
    return sb.ToString ();
}

public static byte[] FromHex (String hex)
{

    // pre-create the array
    int resultLength = hex.Length / 2;
    byte[] result = new byte[resultLength];
    // set validity = 0 (0 = valid, anything else is not valid)
    int validity = 0;
    int c, isLetter, value, validDigitStruct, validDigit, validLetterStruct, validLetter;
    for (int i = 0, hexOffset = 0; i < resultLength; i++, hexOffset += 2) {
        c = hex [hexOffset];

        // check using calculation over bits to see if first char is a letter
        // isLetter is zero if it is a digit, 1 if it is a letter (upper & lowercase)
        isLetter = (c >> 6) & 1;

        // calculate the tuple value using a multiplication to make up the difference between
        // a digit character and an alphanumerical character
        // minus 1 for the fact that the letters are not zero based
        value = ((c & 0xF) + isLetter * (-1 + 10)) << 4;

        // check validity of all the other bits
        validity |= c >> 7; // changed to >>, maybe not OK, use UInt?

        validDigitStruct = (c & 0x30) ^ 0x30;
        validDigit = ((c & 0x8) >> 3) * (c & 0x6);
        validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);

        validLetterStruct = c & 0x18;
        validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
        validity |= isLetter * (validLetterStruct | validLetter);

        // do the same with the lower (less significant) tuple
        c = hex [hexOffset + 1];
        isLetter = (c >> 6) & 1;
        value ^= (c & 0xF) + isLetter * (-1 + 10);
        result [i] = (byte)value;

        // check validity of all the other bits
        validity |= c >> 7; // changed to >>, maybe not OK, use UInt?

        validDigitStruct = (c & 0x30) ^ 0x30;
        validDigit = ((c & 0x8) >> 3) * (c & 0x6);
        validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);

        validLetterStruct = c & 0x18;
        validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
        validity |= isLetter * (validLetterStruct | validLetter);
    }

    if (validity != 0) {
        throw new ArgumentException ("Hexadecimal encoding incorrect for input " + hex);
    }

    return result;
}

Перетворено з коду Java.


Гм, я справді повинен оптимізувати це Char[]та використовувати Charвнутрішньо замість ints ...
Maarten Bodewes

Для C # ініціалізація змінних, де вони використовуються, а не за межами циклу, ймовірно, надає перевагу компілятору для оптимізації. У будь-якому випадку я отримую рівноцінні показники.
Пітер

2

Для виконання я б поїхав з розчином дрфрозенів. Невеликою оптимізацією для декодера може бути використання таблиці для будь-якого символу для позбавлення від "<< 4".

Очевидно, що два виклики методу коштують дорого. Якщо якась перевірка проводиться або на вхідних, або на вихідних даних (це може бути CRC, контрольна сума чи інше), вони if (b == 255)...можуть бути пропущені, а отже, і метод викликає взагалі.

Використання offset++і offsetзамість offsetі offset + 1може дати деяку теоретичну користь , але я підозрюю , що ручки компілятора це краще , ніж у мене.

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

private static readonly byte[] LookupTableHigh = new byte[] {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

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

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

public static byte ToByte(char[] chars, int offset)
{
  return (byte)(LookupHigh(chars[offset++]) | LookupLow(chars[offset]));
}

Це просто в верхній частині моєї голови і не перевірене і не орієнтоване.


1

Ще один варіант для різноманітності:

public static byte[] FromHexString(string src)
{
    if (String.IsNullOrEmpty(src))
        return null;

    int index = src.Length;
    int sz = index / 2;
    if (sz <= 0)
        return null;

    byte[] rc = new byte[sz];

    while (--sz >= 0)
    {
        char lo = src[--index];
        char hi = src[--index];

        rc[sz] = (byte)(
            (
                (hi >= '0' && hi <= '9') ? hi - '0' :
                (hi >= 'a' && hi <= 'f') ? hi - 'a' + 10 :
                (hi >= 'A' && hi <= 'F') ? hi - 'A' + 10 :
                0
            )
            << 4 | 
            (
                (lo >= '0' && lo <= '9') ? lo - '0' :
                (lo >= 'a' && lo <= 'f') ? lo - 'a' + 10 :
                (lo >= 'A' && lo <= 'F') ? lo - 'A' + 10 :
                0
            )
        );
    }

    return rc;          
}

1

Не оптимізовано для швидкості, але більше LINQy, ніж більшість відповідей (.NET 4.0):

<Extension()>
Public Function FromHexToByteArray(hex As String) As Byte()
    hex = If(hex, String.Empty)
    If hex.Length Mod 2 = 1 Then hex = "0" & hex
    Return Enumerable.Range(0, hex.Length \ 2).Select(Function(i) Convert.ToByte(hex.Substring(i * 2, 2), 16)).ToArray
End Function

<Extension()>
Public Function ToHexString(bytes As IEnumerable(Of Byte)) As String
    Return String.Concat(bytes.Select(Function(b) b.ToString("X2")))
End Function

1

Дві машупи, які складають дві операції кусання в одну.

Напевно, досить ефективна версія:

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

Декадентська версія linq-bit-hacking:

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

І зворотне:

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

1
HexStringToByteArray ("09") повертає 0x02, що погано
CoperNick

1

Інший спосіб - stackallocзменшити тиск пам'яті GC:

static string ByteToHexBitFiddle(byte[] bytes)
{
        var c = stackalloc char[bytes.Length * 2 + 1];
        int b; 
        for (int i = 0; i < bytes.Length; ++i)
        {
            b = bytes[i] >> 4;
            c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
            b = bytes[i] & 0xF;
            c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
        }
        c[bytes.Length * 2 ] = '\0';
        return new string(c);
}

1

Ось мій постріл на це. Я створив пару класів розширень, щоб розширити рядок і байт. У великих тестових файлах продуктивність порівнянна з маніпуляцією байтами 2.

Нижче наведений код для ToHexString - це оптимізована реалізація алгоритму пошуку та зсуву. Він майже ідентичний тому, що Бехрооз, але виявляється, що використовувати foreachітерацію і лічильник швидше, ніж явно індексувати for.

Він займає 2-е місце позаду Byte Manipulation 2 на моїй машині і дуже читабельний код. Наступні результати тесту також цікаві:

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

Виходячи з вищенаведених результатів, можна зробити висновок, що:

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

Ось код:

using System;

namespace ConversionExtensions
{
    public static class ByteArrayExtensions
    {
        private readonly static char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

        public static string ToHexString(this byte[] bytes)
        {
            char[] hex = new char[bytes.Length * 2];
            int index = 0;

            foreach (byte b in bytes)
            {
                hex[index++] = digits[b >> 4];
                hex[index++] = digits[b & 0x0F];
            }

            return new string(hex);
        }
    }
}


using System;
using System.IO;

namespace ConversionExtensions
{
    public static class StringExtensions
    {
        public static byte[] ToBytes(this string hexString)
        {
            if (!string.IsNullOrEmpty(hexString) && hexString.Length % 2 != 0)
            {
                throw new FormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");
            }

            hexString = hexString.ToUpperInvariant();
            byte[] data = new byte[hexString.Length / 2];

            for (int index = 0; index < hexString.Length; index += 2)
            {
                int highDigitValue = hexString[index] <= '9' ? hexString[index] - '0' : hexString[index] - 'A' + 10;
                int lowDigitValue = hexString[index + 1] <= '9' ? hexString[index + 1] - '0' : hexString[index + 1] - 'A' + 10;

                if (highDigitValue < 0 || lowDigitValue < 0 || highDigitValue > 15 || lowDigitValue > 15)
                {
                    throw new FormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");
                }
                else
                {
                    byte value = (byte)((highDigitValue << 4) | (lowDigitValue & 0x0F));
                    data[index / 2] = value;
                }
            }

            return data;
        }
    }
}

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

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

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

Перетворення масиву байтів у шістнадцяткове представлення рядків


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

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

ByteArrayToHexViaLookup: 55509,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 588,77 середніх тиків (понад 1000 пробігів), 2,8X

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

ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382 623,44 середніх тиків (понад 1000 пробігів), 2.3X

ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818,111,95 середніх тиків (понад 1000 пробіжок), 1,1X

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

ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867 303,98 середніх тиків (понад 1000 пробіжок), 1,0X

ByteArrayToHexStringViaStringJoinArrayConvertAll: 882,710,28 середніх тиків (понад 1000 пробіжок), 1,0X



1

Ще одна швидка функція ...

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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.