Как можно преобразовать байтовый массив в шестнадцатеричную строку и наоборот?
Как можно преобразовать байтовый массив в шестнадцатеричную строку и наоборот?
Ответы:
Или:
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
или:
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
Есть еще больше вариантов сделать это, например, здесь .
Обратное преобразование будет выглядеть так:
public static byte[] StringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
Использование Substring
- лучший вариант в сочетании с Convert.ToByte
. Смотрите этот ответ для получения дополнительной информации. Если вам нужна лучшая производительность, вы должны избегать, Convert.ToByte
прежде чем вы можете упасть SubString
.
Примечание: новый лидер с 2015-08-20.
Я проверил каждый из различных методов преобразования через некоторое грубое Stopwatch
тестирование производительности, прогон со случайным предложением (n = 61, 1000 итераций) и прогон с текстом Project Gutenburg (n = 1 238 957, 150 итераций). Вот результаты, примерно от самого быстрого до самого медленного. Все измерения даны в тиках ( 10000 тиков = 1 мс ), и все относительные ноты сравниваются с [самой медленной] StringBuilder
реализацией. Используемый код см. Ниже или в репозитории тестовой инфраструктуры, где я сейчас поддерживаю код для запуска этого.
ВНИМАНИЕ: не полагайтесь на эти характеристики для чего-то конкретного; это просто пример пробных данных. Если вам действительно нужна первоклассная производительность, протестируйте эти методы в среде, представляющей ваши производственные потребности, с данными, указывающими, что вы будете использовать.
unsafe
(через CodesInChaos) (добавлен в тестовое репо airbreather )
BitConverter
(через Томалак)
{SoapHexBinary}.ToString
(через Майкрофт)
{byte}.ToString("X2")
(используя foreach
) (получено из ответа Уилла Дина)
{byte}.ToString("X2")
(используя {IEnumerable}.Aggregate
, требует System.Linq) (через Марк)
Array.ConvertAll
(используя string.Join
) (через Уилла Дина)
Array.ConvertAll
(используется string.Concat
, требуется .NET 4.0) (через Уилла Дина)
{StringBuilder}.AppendFormat
(используя foreach
) (через Томалак)
{StringBuilder}.AppendFormat
(использование {IEnumerable}.Aggregate
, требует System.Linq) (получено из ответа Томалака)
Таблицы поиска взяли на себя инициативу по манипулированию байтами. По сути, существует некоторая форма предварительного вычисления того, какой любой данный клев или байт будет в шестнадцатеричном виде. Затем, просматривая данные, вы просто просматриваете следующую часть, чтобы увидеть, какой это будет шестнадцатеричная строка. Это значение затем добавляется к полученному выводу строки некоторым способом. Долгое время манипулирование байтами, потенциально трудное для чтения некоторыми разработчиками, было наиболее эффективным подходом.
Ваша лучшая ставка по-прежнему будет найти некоторые репрезентативные данные и опробовать их в производственной среде. Если у вас разные ограничения памяти, вы можете предпочесть метод с меньшим количеством выделений, чем метод, который был бы быстрее, но потреблял бы больше памяти.
Не стесняйтесь играть с кодом тестирования, который я использовал. Версия включена здесь, но не стесняйтесь клонировать репо и добавлять свои собственные методы. Пожалуйста, отправьте запрос на удаление, если вы найдете что-нибудь интересное или хотите помочь улучшить используемую им инфраструктуру тестирования.
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
все же выигрывает.
В SoapHexBinary
анализ добавлен ответ Майкрофта , который занял третье место.
Добавлен ответ CodesInChaos по манипулированию байтами, который занял первое место (с большим запасом для больших блоков текста).
Добавлен ответ поиска Натана Моинвазири и вариант из блога Брайана Ламберта. Оба довольно быстрые, но не идут впереди на тестовой машине, которую я использовал (AMD Phenom 9750).
Добавлен новый байтовый ответ @ CodesInChaos. Похоже, что он взял на себя инициативу как по тестам предложений, так и по полнотекстовым тестам.
Добавлены оптимизации и варианты airbreatherunsafe
к репо этого ответа . Если вы хотите играть в небезопасную игру, вы можете получить огромный выигрыш в производительности по сравнению с любым из предыдущих лучших победителей как в коротких строках, так и в больших текстах.
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")));
(Последнее из комментария к оригинальному сообщению.)
Еще один подход на основе таблицы поиска. Этот использует только одну таблицу поиска для каждого байта вместо таблицы поиска для каждого куска.
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-FF
Дополнительная информация: метод BitConverter.ToString (Byte [])
Я только что столкнулся с той же проблемой сегодня, и я наткнулся на этот код:
private static string ByteArrayToHex(byte[] barray)
{
char[] c = new char[barray.Length * 2];
byte b;
for (int i = 0; i < barray.Length; ++i)
{
b = ((byte)(barray[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
Источник: сообщение на форуме byte [] Array to Hex String (см. Сообщение PZahra). Я немного изменил код, чтобы удалить префикс 0x.
Я провел некоторое тестирование производительности в коде, и это было почти в восемь раз быстрее, чем при использовании BitConverter.ToString () (самый быстрый согласно сообщению Патриджа).
Это ответ на пересмотр 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
Да!
Реквизит в Partridge для каркаса скамейки, его легко взломать. В качестве входных данных используется следующий хэш 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). Методы для де / кодирования потоков или массивов должны быть реализованы, это просто подтверждение концепции.
Это отличная статья. Мне нравится решение Валида. Я не прошел тест Патриджа, но, похоже, он проходит довольно быстро. Мне также потребовался обратный процесс, преобразование шестнадцатеричной строки в байтовый массив, поэтому я написал ее как обращение решения Валида. Не уверен, что это быстрее, чем оригинальное решение Томалака. Опять же, я не запускал обратный процесс через тест Патриджа.
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;
}
}
}
КСТАТИ для тестирования производительности инициализация алфавита каждый раз, когда вызываемая функция преобразования неверна, алфавит должен быть const (для строки) или статическим readonly (для char []). Затем основанное на алфавите преобразование byte [] в строку становится таким же быстрым, как версии манипулирования байтами.
И, конечно, тест должен быть скомпилирован в Release (с оптимизацией) и с отключенной опцией «Отключить оптимизацию JIT» (то же самое для «Enable Just My Code», если код должен быть отлаживаемым).
Обратная функция для кода Валида Эйссы (Hex String To Byte Array):
public static byte[] HexToBytes(this string hexString)
{
byte[] b = new byte[hexString.Length / 2];
char c;
for (int i = 0; i < hexString.Length / 2; i++)
{
c = hexString[i * 2];
b[i] = (byte)((c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57)) << 4);
c = hexString[i * 2 + 1];
b[i] += (byte)(c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57));
}
return b;
}
Функция Waleed Eissa с поддержкой нижнего регистра:
public static string BytesToHex(this byte[] barray, bool toLowerCase = true)
{
byte addByte = 0x37;
if (toLowerCase) addByte = 0x57;
char[] c = new char[barray.Length * 2];
byte b;
for (int i = 0; i < barray.Length; ++i)
{
b = ((byte)(barray[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + addByte : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + addByte : b + 0x30);
}
return new string(c);
}
Методы расширения (отказ от ответственности: полностью непроверенный код, кстати ...):
public static class ByteExtensions
{
public static string ToHexString(this byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
{
hex.AppendFormat("{0:x2}", b);
}
return hex.ToString();
}
}
и т. д. Используйте одно из трех решений Томалака (последнее из которых является методом расширения строки).
От разработчиков 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
.
Однако я добился определенного успеха, взяв несколько подсказок из кода Waleeds и собрав их вместе. Это ужасно ужасно, но, кажется, работает и работает в 1/3 времени по сравнению с другими, согласно моим тестам (с использованием механизма тестирования патронов). В зависимости от размера ввода. Переключение вокруг?: S для выделения 0-9 первым, вероятно, приведет к несколько более быстрому результату, поскольку в нем больше цифр, чем букв.
public static byte[] StringToByteArray2(string hex)
{
byte[] bytes = new byte[hex.Length/2];
int bl = bytes.Length;
for (int i = 0; i < bl; ++i)
{
bytes[i] = (byte)((hex[2 * i] > 'F' ? hex[2 * i] - 0x57 : hex[2 * i] > '9' ? hex[2 * i] - 0x37 : hex[2 * i] - 0x30) << 4);
bytes[i] |= (byte)(hex[2 * i + 1] > 'F' ? hex[2 * i + 1] - 0x57 : hex[2 * i + 1] > '9' ? hex[2 * i + 1] - 0x37 : hex[2 * i + 1] - 0x30);
}
return bytes;
}
Эта версия ByteArrayToHexViaByteManipulation может быть быстрее.
Из моих отчетов:
...
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
внутренне вместо целых ...
Для производительности я бы пошел с раствором drphrozens. Небольшой оптимизацией для декодера может быть использование таблицы для любого символа, чтобы избавиться от «<< 4».
Очевидно, что два вызова метода являются дорогостоящими. Если какая-либо проверка выполняется на входных или выходных данных (может быть CRC, контрольная сумма или что-то еще),if (b == 255)...
можно пропустить и, таким образом, также вызвать метод в целом.
Использование offset++
и offset
вместо offset
и offset + 1
может дать некоторое теоретическое преимущество, но я подозреваю, что компилятор справляется с этим лучше, чем я.
private static readonly byte[] LookupTableLow = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static readonly byte[] LookupTableHigh = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static byte LookupLow(char c)
{
var b = LookupTableLow[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
private static byte LookupHigh(char c)
{
var b = LookupTableHigh[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
public static byte ToByte(char[] chars, int offset)
{
return (byte)(LookupHigh(chars[offset++]) | LookupLow(chars[offset]));
}
Это только с моей головы и не было проверено или сравнительно.
Еще один вариант разнообразия:
public static byte[] FromHexString(string src)
{
if (String.IsNullOrEmpty(src))
return null;
int index = src.Length;
int sz = index / 2;
if (sz <= 0)
return null;
byte[] rc = new byte[sz];
while (--sz >= 0)
{
char lo = src[--index];
char hi = src[--index];
rc[sz] = (byte)(
(
(hi >= '0' && hi <= '9') ? hi - '0' :
(hi >= 'a' && hi <= 'f') ? hi - 'a' + 10 :
(hi >= 'A' && hi <= 'F') ? hi - 'A' + 10 :
0
)
<< 4 |
(
(lo >= '0' && lo <= '9') ? lo - '0' :
(lo >= 'a' && lo <= 'f') ? lo - 'a' + 10 :
(lo >= 'A' && lo <= 'F') ? lo - 'A' + 10 :
0
)
);
}
return rc;
}
Не оптимизирован для скорости, но больше LINQy, чем большинство ответов (.NET 4.0):
<Extension()>
Public Function FromHexToByteArray(hex As String) As Byte()
hex = If(hex, String.Empty)
If hex.Length Mod 2 = 1 Then hex = "0" & hex
Return Enumerable.Range(0, hex.Length \ 2).Select(Function(i) Convert.ToByte(hex.Substring(i * 2, 2), 16)).ToArray
End Function
<Extension()>
Public Function ToHexString(bytes As IEnumerable(Of Byte)) As String
Return String.Concat(bytes.Select(Function(b) b.ToString("X2")))
End Function
Два коллажа, которые складывают две клевы в одну.
Вероятно, довольно эффективная версия:
public static string ByteArrayToString2(byte[] ba)
{
char[] c = new char[ba.Length * 2];
for( int i = 0; i < ba.Length * 2; ++i)
{
byte b = (byte)((ba[i>>1] >> 4*((i&1)^1)) & 0xF);
c[i] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string( c );
}
Декадентская версия linq-with-bit-hacking:
public static string ByteArrayToString(byte[] ba)
{
return string.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) );
}
И наоборот:
public static byte[] HexStringToByteArray( string s )
{
byte[] ab = new byte[s.Length>>1];
for( int i = 0; i < s.Length; i++ )
{
int b = s[i];
b = (b - '0') + ((('9' - b)>>31)&-7);
ab[i>>1] |= (byte)(b << 4*((i&1)^1));
}
return ab;
}
Другой способ - использовать stackalloc
для уменьшения давления памяти GC:
static string ByteToHexBitFiddle(byte[] bytes)
{
var c = stackalloc char[bytes.Length * 2 + 1];
int b;
for (int i = 0; i < bytes.Length; ++i)
{
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
}
c[bytes.Length * 2 ] = '\0';
return new string(c);
}
Вот мой выстрел в это. Я создал пару классов расширения для расширения строки и байта. При тестировании больших файлов производительность сопоставима с Byte Manipulation 2.
Код ниже для ToHexString - оптимизированная реализация алгоритма поиска и сдвига. Он почти идентичен Behrooz, но оказывается, что он использует foreach
итерацию, а счетчик работает быстрее, чем явное индексирование.for
.
Он занимает второе место после Byte Manipulation 2 на моей машине и является очень читабельным кодом. Следующие результаты испытаний также представляют интерес:
ToHexStringCharArrayWithCharArrayLookup: 41 589,69 средних тиков (более 1000 прогонов), 1,5X ToHexStringCharArrayWithStringLookup: 50 764,06 средних тиков (более 1000 прогонов), 1,2X
Исходя из приведенных выше результатов, можно с уверенностью заключить, что:
Вот код:
using System;
namespace ConversionExtensions
{
public static class ByteArrayExtensions
{
private readonly static char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
public static string ToHexString(this byte[] bytes)
{
char[] hex = new char[bytes.Length * 2];
int index = 0;
foreach (byte b in bytes)
{
hex[index++] = digits[b >> 4];
hex[index++] = digits[b & 0x0F];
}
return new string(hex);
}
}
}
using System;
using System.IO;
namespace ConversionExtensions
{
public static class StringExtensions
{
public static byte[] ToBytes(this string hexString)
{
if (!string.IsNullOrEmpty(hexString) && hexString.Length % 2 != 0)
{
throw new FormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");
}
hexString = hexString.ToUpperInvariant();
byte[] data = new byte[hexString.Length / 2];
for (int index = 0; index < hexString.Length; index += 2)
{
int highDigitValue = hexString[index] <= '9' ? hexString[index] - '0' : hexString[index] - 'A' + 10;
int lowDigitValue = hexString[index + 1] <= '9' ? hexString[index + 1] - '0' : hexString[index + 1] - 'A' + 10;
if (highDigitValue < 0 || lowDigitValue < 0 || highDigitValue > 15 || lowDigitValue > 15)
{
throw new FormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");
}
else
{
byte value = (byte)((highDigitValue << 4) | (lowDigitValue & 0x0F));
data[index / 2] = value;
}
}
return data;
}
}
}
Ниже приведены результаты тестирования, которые я получил, когда поместил свой код в проект тестирования @ patridge на моей машине. Я также добавил тест для преобразования байтового массива из шестнадцатеричного. Тестовые прогоны, которые выполняли мой код: ByteArrayToHexViaOptimizedLookupAndShift и HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte был взят из XXXX. HexToByteArrayViaSoapHexBinary - это ответ @ Mykroft.
Процессор Intel Pentium III Xeon
Cores: 4 <br/> Current Clock Speed: 1576 <br/> Max Clock Speed: 3092 <br/>
Преобразование массива байтов в шестнадцатеричное строковое представление
ByteArrayToHexViaByteManipulation2: 39 366,64 средних тиков (более 1000 пробежек), 22,4X
ByteArrayToHexViaOptimizedLookupAndShift: 41 588,64 средних тиков (более 1000 прогонов), 21,2X
ByteArrayToHexViaLookup: 55 509,56 средних тиков (более 1000 прогонов), 15,9X
ByteArrayToHexViaByteManipulation: 65 349,12 средних тиков (более 1000 прогонов), 13,5X
ByteArrayToHexViaLookupAndShift: 86 926,87 средних тиков (более 1000 пробежек), 10,2X
ByteArrayToHexStringViaBitConverter: 139 353,73 средних тиков (более 1000 прогонов), 6,3X
ByteArrayToHexViaSoapHexBinary: 314 598,77 средних тиков (более 1000 пробежек), 2,8X
ByteArrayToHexStringViaStringBuilderForEachByteToString: 344 264,63 средних тиков (более 1000 прогонов), 2,6X
ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382 623,44 средних тиков (более 1000 прогонов), 2,3X
ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818 111,95 средних тиков (более 1000 прогонов), 1,1X
ByteArrayToHexStringViaStringConcatArrayConvertAll: 839 244,84 средних тиков (более 1000 прогонов), 1,1X
ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867 303,98 средних тиков (более 1000 прогонов), 1,0X
ByteArrayToHexStringViaStringJoinArrayConvertAll: 882 710,28 средних тиков (более 1000 прогонов), 1,0X
Еще одна быстрая функция ...
private static readonly byte[] HexNibble = new byte[] {
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
};
public static byte[] HexStringToByteArray( string str )
{
int byteCount = str.Length >> 1;
byte[] result = new byte[byteCount + (str.Length & 1)];
for( int i = 0; i < byteCount; i++ )
result[i] = (byte) (HexNibble[str[i << 1] - 48] << 4 | HexNibble[str[(i << 1) + 1] - 48]);
if( (str.Length & 1) != 0 )
result[byteCount] = (byte) HexNibble[str[str.Length - 1] - 48];
return result;
}