Як можна перетворити байтовий масив у шістнадцятковий рядок і навпаки?
Як можна перетворити байтовий масив у шістнадцятковий рядок і навпаки?
Відповіді:
Або:
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
або:
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
Є ще більше варіантів цього, наприклад, тут .
Зворотне перетворення піде так:
public static byte[] StringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
Використання Substring
- найкращий варіант у поєднанні з Convert.ToByte
. Дивіться цю відповідь для отримання додаткової інформації. Якщо вам потрібна краща продуктивність, ви повинні уникати, Convert.ToByte
перш ніж ви зможете скинути її SubString
.
Примітка: новий керівник станом на 20.08.2015.
Я провів кожен з різних методів перетворення за допомогою певного Stopwatch
тестування грубої продуктивності, пробігу з випадковим реченням (n = 61, 1000 ітерацій) та запуску з текстом проекту Гутенбурга (n = 1,238,957, 150 ітерацій). Ось результати, приблизно від найшвидшого до найповільнішого. Всі вимірювання проводяться в тиках ( 10000 тиків = 1 мс ), і всі відносні замітки порівнюються з [найповільнішим] StringBuilder
виконанням. Про використаний код див. Нижче або тест-рамковий репо, де я зараз підтримую код для цього.
ПОПЕРЕДЖЕННЯ: Не покладайтеся на цю статистику на щось конкретне; вони є просто типовим циклом вибіркових даних. Якщо вам справді потрібні найвищі показники, будь ласка, протестуйте ці методи в середовищі, що відповідає вашим виробничим потребам, з даними, що представляють, що ви будете використовувати.
unsafe
(через CodesInChaos) (додано до тестового репо за допомогою повітряного дихання )
BitConverter
(через Томалак)
{SoapHexBinary}.ToString
(через Mykroft)
{byte}.ToString("X2")
(використовуючи foreach
) (отриманий з відповіді Вілла Діна)
{byte}.ToString("X2")
(використовує {IEnumerable}.Aggregate
, вимагає System.Linq) (через Марк)
Array.ConvertAll
(за допомогою string.Join
) (через Will Dean)
Array.ConvertAll
(використовує string.Concat
, вимагає .NET 4.0) (через Will Dean)
{StringBuilder}.AppendFormat
(за допомогою foreach
) (через Tomalak)
{StringBuilder}.AppendFormat
(використовує {IEnumerable}.Aggregate
, вимагає System.Linq) (випливає з відповіді Томалака)
Таблиці пошуку взяли на себе перевагу над маніпулюванням байтами. В основному, існує певна форма попереднього обчислення того, що будь-який заданий nibble або byte буде в шістнадцятковій. Потім, переглядаючи дані, ви просто шукаєте наступну частину, щоб побачити, якою вона буде. Потім це значення певним чином додається до отриманого рядка. Тривалий час маніпуляції з байтом, що потенційно важче було прочитати деякими розробниками, був найефективнішим підходом.
Ваша найкраща ставка все ще буде знаходити деякі репрезентативні дані та випробовувати їх у виробничих умовах. Якщо у вас є різні обмеження пам'яті, ви можете віддати перевагу методу з меншим розміщенням розподілу, який був би швидшим, але витратив більше пам'яті.
Не соромтеся грати з тестовим кодом, який я використав. Тут включена версія, але сміливо клонуйте репо і додайте свої власні методи. Будь ласка, надішліть запит на витяг, якщо ви виявите щось цікаве або хочете допомогти вдосконалити тестові рамки, які він використовує.
Func<byte[], string>
) до /Tests/ConvertByteArrayToHexString/Test.cs.TestCandidates
значення, що повертається в тому самому класі.GenerateTestInput
в цьому ж класі.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();
}
Додана відповідь Уеліда до аналізу. Досить швидко.
Доданий string.Concat
Array.ConvertAll
варіант для повноти (потрібен .NET 4.0). Нарівні з string.Join
версією.
Тестовий репо включає більше варіантів, таких як StringBuilder.Append(b.ToString("X2"))
. Жоден результат не порушив. foreach
швидше, ніж {IEnumerable}.Aggregate
, наприклад, але BitConverter
все-таки виграє.
Додано відповідь Mykroft SoapHexBinary
до аналізу, який посів третє місце.
Додано відповідь на маніпуляцію байтами CodesInChaos, яка посіла перше місце (з великим запасом на великих блоках тексту).
Додано відповідь на пошук Натана Моінвазірі та варіант із блогу Брайана Ламберта. І те, і інше досить швидко, але не переймаючи лідерства на тестовій машині, яку я використав (AMD Phenom 9750).
Додано нову відповідь пошуку на байті @ CodesInChaos. Схоже, він взяв на себе ініціативу як на тести речень, так і на повнотекстові тести.
До репо-відповіді цієї відповіді додано оптимізацію та unsafe
варіант повітряного дихача . Якщо ви хочете грати в небезпечну гру, ви можете отримати величезні результати від будь-якого з попередніх головних переможців як на коротких рядках, так і на великих текстах.
bytes.ToHexStringAtLudicrousSpeed()
).
Існує клас під назвою 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);
}
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn
Відмовтеся від усієї надії, ви, хто сюди входить
Пояснення дивного біда:
bytes[i] >> 4
витягує високу плетіння байта, bytes[i] & 0xF
витягує нижню клітку байтаb - 10
< 0
для значень b < 10
, які стануть десятковою цифрою, >= 0
для значень b > 10
, які стануть літерою від A
до F
.i >> 31
32-бітового цілого цілого числа витягує знак завдяки розширення знаку. Це буде -1
за i < 0
і 0
для i >= 0
.(b-10)>>31
буде 0
для літер і -1
для цифр.0
і b
знаходиться в діапазоні від 10 до 15. Ми хочемо відобразити її на A
(65) - F
(70), що означає додавання 55 ( 'A'-10
).b
від діапазону від 0 до 9 до діапазону 0
(48) до 9
(57). Це означає, що йому потрібно стати -7 ( '0' - 55
). & -7
з (0 & -7) == 0
і (-1 & -7) == -7
.Деякі додаткові міркування:
c
, оскільки вимірювання показує, що обчислити її i
дешевше.i < bytes.Length
як верхньої межі циклу дозволяє JITter усунути перевірку меж bytes[i]
, тому я вибрав такий варіант.b
int дозволяє робити непотрібні перетворення з байта в байт.hex string
до byte[] array
?
87 + b + (((b-10)>>31)&-39)
byte[] array
", що буквально означає масив байтових масивів, або byte[][]
. Я просто забавлявся.
Якщо ви хочете більшої гнучкості 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")));
(Остання з коментаря до оригінальної публікації.)
Ще один підхід на основі таблиці пошуку. Цей використовує лише одну таблицю пошуку для кожного байту, а не таблицю пошуку на 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;
}
Span
можна зараз використовувати замість unsafe
??
Ви можете використовувати метод 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 (байт [])
Я щойно стикався з цією ж проблемою сьогодні, і мені траплявся цей код:
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 () (найшвидший відповідно до публікації патронта).
Це відповідь на перегляд 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
Веселіться! (Але оптимізуйте з помірністю.)
Доповнення до відповіді від @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...
).
Цю проблему можна було також вирішити за допомогою таблиці пошуку. Для цього знадобиться невелика кількість статичної пам’яті і для кодера, і для декодера. Однак цей метод буде швидким:
Моє рішення використовує 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). Методи де-кодування потоків чи масивів повинні бути впроваджені, це лише доказ концепції.
Це чудовий пост. Мені подобається рішення Уеліда. Я не запускав це через тест патронта, але це здається досить швидким. Також мені знадобився зворотний процес, перетворивши шістнадцяткову рядок у байтовий масив, тому я записав це як обернення рішення 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;
}
hexString[i] &= ~0x20;
Навіщо робити це складним? У Visual Studio 2008 це просто:
C #:
string hex = BitConverter.ToString(YourByteArray).Replace("-", "");
VB:
Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Не купувати відповіді на багато відповідей тут, але я знайшов досить оптимальну (~ 4,5 рази краще, ніж прийнято), просту реалізацію шестнадцяткового рядкового аналізатора. По-перше, вихід з моїх тестів (перша партія - це моя реалізація):
Give me that string:
04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939f
Time to parse 100,000 times: 50.4192 ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Accepted answer: (StringToByteArray)
Time to parse 100000 times: 233.1264ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With Mono's implementation:
Time to parse 100000 times: 777.2544ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With SoapHexBinary:
Time to parse 100000 times: 845.1456ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Рядки base64 та "BitConverter'd" є для перевірки правильності. Зауважте, що вони рівні.
Впровадження:
public static byte[] ToByteArrayFromHex(string hexString)
{
if (hexString.Length % 2 != 0) throw new ArgumentException("String must have an even length");
var array = new byte[hexString.Length / 2];
for (int i = 0; i < hexString.Length; i += 2)
{
array[i/2] = ByteFromTwoChars(hexString[i], hexString[i + 1]);
}
return array;
}
private static byte ByteFromTwoChars(char p, char p_2)
{
byte ret;
if (p <= '9' && p >= '0')
{
ret = (byte) ((p - '0') << 4);
}
else if (p <= 'f' && p >= 'a')
{
ret = (byte) ((p - 'a' + 10) << 4);
}
else if (p <= 'F' && p >= 'A')
{
ret = (byte) ((p - 'A' + 10) << 4);
} else throw new ArgumentException("Char is not a hex digit: " + p,"p");
if (p_2 <= '9' && p_2 >= '0')
{
ret |= (byte) ((p_2 - '0'));
}
else if (p_2 <= 'f' && p_2 >= 'a')
{
ret |= (byte) ((p_2 - 'a' + 10));
}
else if (p_2 <= 'F' && p_2 >= 'A')
{
ret |= (byte) ((p_2 - 'A' + 10));
} else throw new ArgumentException("Char is not a hex digit: " + p_2, "p_2");
return ret;
}
Я спробував деякі речі unsafe
і перемістив (явно зайву) if
послідовність передачі символів на інший метод, але це було найшвидшим.
(Я визнаю, що це відповідає половині запитання. Я відчував, що перетворення string-> byte [] недостатньо представлене, тоді як кут рядка byte [] -> здається добре висвітленим. Таким чином, ця відповідь.)
Безпечні версії:
public static class HexHelper
{
[System.Diagnostics.Contracts.Pure]
public static string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string hexAlphabet = @"0123456789ABCDEF";
var chars = new char[checked(value.Length * 2)];
unchecked
{
for (int i = 0; i < value.Length; i++)
{
chars[i * 2] = hexAlphabet[value[i] >> 4];
chars[i * 2 + 1] = hexAlphabet[value[i] & 0xF];
}
}
return new string(chars);
}
[System.Diagnostics.Contracts.Pure]
public static byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = value[i * 2]; // High 4 bits.
int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
b = value[i * 2 + 1]; // Low 4 bits.
val += (b - '0') + ((('9' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
return result;
}
}
}
Небезпечні версії Для тих, хто вважає за краще продуктивність і не боїться небезпеки. Близько на 35% швидший ToHex і на 10% швидший FromHex.
public static class HexUnsafeHelper
{
[System.Diagnostics.Contracts.Pure]
public static unsafe string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string alphabet = @"0123456789ABCDEF";
string result = new string(' ', checked(value.Length * 2));
fixed (char* alphabetPtr = alphabet)
fixed (char* resultPtr = result)
{
char* ptr = resultPtr;
unchecked
{
for (int i = 0; i < value.Length; i++)
{
*ptr++ = *(alphabetPtr + (value[i] >> 4));
*ptr++ = *(alphabetPtr + (value[i] & 0xF));
}
}
}
return result;
}
[System.Diagnostics.Contracts.Pure]
public static unsafe byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
fixed (char* valuePtr = value)
{
char* valPtr = valuePtr;
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = *valPtr++; // High 4 bits.
int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
b = *valPtr++; // Low 4 bits.
val += (b - '0') + ((('9' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
}
return result;
}
}
}
BTW Для тестового тестування ініціалізації алфавіту кожного разу, коли функція перетворення, що викликається, неправильна, алфавіт повинен бути const (для рядка) або статичним читанням лише для char []). Тоді перетворення байта [] на основі алфавіту в рядок стає настільки ж швидким, як і версії маніпулювання байтом.
І звичайно тест повинен бути складений у Release (з оптимізацією) та з вимкненою опцією "Suppress JIT optimization" (те саме для "Enable Just My Code", якщо код повинен бути налагодженим).
Зворотна функція для коду 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);
}
Методи розширення (відмова від відповідальності: повністю неперевірений код, 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 (останнє - метод розширення на рядку).
Гарне, просте перетворення від розробників 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 для себе.
b.ToSting("X2")
.
А для вставки в рядок SQL (якщо ви не використовуєте командні параметри):
public static String ByteArrayToSQLHexString(byte[] Source)
{
return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
Source == null
або у Source.Length == 0
нас є проблема , сер!
Що стосується швидкості, то, здається, це краще за все тут:
public static string ToHexString(byte[] data) {
byte b;
int i, j, k;
int l = data.Length;
char[] r = new char[l * 2];
for (i = 0, j = 0; i < l; ++i) {
b = data[i];
k = b >> 4;
r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
k = b & 15;
r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
}
return new string(r);
}
Я не отримав код, який ти запропонував працювати, Оліпро. hex[i] + hex[i+1]
мабуть, повернув int
.
Але я мав певний успіх, взявши підказки з коду Уеліда і забивши це разом. Це некрасиво, як пекло, але, здається, працює і працює в 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;
}
Ця версія маніпуляції ByteArrayToHexViaByte може бути швидшою.
З моїх звітів:
...
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.
Char[]
та використовувати Char
внутрішньо замість ints ...
Для виконання я б поїхав з розчином дрфрозенів. Невеликою оптимізацією для декодера може бути використання таблиці для будь-якого символу для позбавлення від "<< 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]));
}
Це просто в верхній частині моєї голови і не перевірене і не орієнтоване.
Ще один варіант для різноманітності:
public static byte[] FromHexString(string src)
{
if (String.IsNullOrEmpty(src))
return null;
int index = src.Length;
int sz = index / 2;
if (sz <= 0)
return null;
byte[] rc = new byte[sz];
while (--sz >= 0)
{
char lo = src[--index];
char hi = src[--index];
rc[sz] = (byte)(
(
(hi >= '0' && hi <= '9') ? hi - '0' :
(hi >= 'a' && hi <= 'f') ? hi - 'a' + 10 :
(hi >= 'A' && hi <= 'F') ? hi - 'A' + 10 :
0
)
<< 4 |
(
(lo >= '0' && lo <= '9') ? lo - '0' :
(lo >= 'a' && lo <= 'f') ? lo - 'a' + 10 :
(lo >= 'A' && lo <= 'F') ? lo - 'A' + 10 :
0
)
);
}
return rc;
}
Не оптимізовано для швидкості, але більше LINQy, ніж більшість відповідей (.NET 4.0):
<Extension()>
Public Function FromHexToByteArray(hex As String) As Byte()
hex = If(hex, String.Empty)
If hex.Length Mod 2 = 1 Then hex = "0" & hex
Return Enumerable.Range(0, hex.Length \ 2).Select(Function(i) Convert.ToByte(hex.Substring(i * 2, 2), 16)).ToArray
End Function
<Extension()>
Public Function ToHexString(bytes As IEnumerable(Of Byte)) As String
Return String.Concat(bytes.Select(Function(b) b.ToString("X2")))
End Function
Дві машупи, які складають дві операції кусання в одну.
Напевно, досить ефективна версія:
public static string ByteArrayToString2(byte[] ba)
{
char[] c = new char[ba.Length * 2];
for( int i = 0; i < ba.Length * 2; ++i)
{
byte b = (byte)((ba[i>>1] >> 4*((i&1)^1)) & 0xF);
c[i] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string( c );
}
Декадентська версія linq-bit-hacking:
public static string ByteArrayToString(byte[] ba)
{
return string.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) );
}
І зворотне:
public static byte[] HexStringToByteArray( string s )
{
byte[] ab = new byte[s.Length>>1];
for( int i = 0; i < s.Length; i++ )
{
int b = s[i];
b = (b - '0') + ((('9' - b)>>31)&-7);
ab[i>>1] |= (byte)(b << 4*((i&1)^1));
}
return ab;
}
Інший спосіб - stackalloc
зменшити тиск пам'яті GC:
static string ByteToHexBitFiddle(byte[] bytes)
{
var c = stackalloc char[bytes.Length * 2 + 1];
int b;
for (int i = 0; i < bytes.Length; ++i)
{
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
}
c[bytes.Length * 2 ] = '\0';
return new string(c);
}
Ось мій постріл на це. Я створив пару класів розширень, щоб розширити рядок і байт. У великих тестових файлах продуктивність порівнянна з маніпуляцією байтами 2.
Нижче наведений код для ToHexString - це оптимізована реалізація алгоритму пошуку та зсуву. Він майже ідентичний тому, що Бехрооз, але виявляється, що використовувати 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)
Виходячи з вищенаведених результатів, можна зробити висновок, що:
Ось код:
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
Ще одна швидка функція ...
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;
}