Принятый ответ, приведенный ниже, выделяет ужасное количество строк в преобразовании строки в байты. Мне интересно, как это влияет на производительность
Вим Коенен
9
Класс SoapHexBinary делает именно то, что вы хотите, я думаю.
Майкрофт
Мне кажется, что задавать 2 вопроса в 1 посте не совсем стандартно.
SandRock
Ответы:
1353
Или:
publicstaticstringByteArrayToString(byte[] ba){StringBuilder hex =newStringBuilder(ba.Length*2);foreach(byte b in ba)
hex.AppendFormat("{0:x2}", b);return hex.ToString();}
Есть еще больше вариантов сделать это, например, здесь .
Обратное преобразование будет выглядеть так:
publicstaticbyte[]StringToByteArray(String hex){intNumberChars= hex.Length;byte[] bytes =newbyte[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.
Вы используете SubString. Разве этот цикл не выделяет ужасное количество строковых объектов?
Вим Коенен
30
Честно говоря, до тех пор, пока производительность не резко упадет, я склонен игнорировать это и доверять среде исполнения и GC позаботиться об этом.
Томалак
87
Поскольку байт - это два полубайта, любая шестнадцатеричная строка, которая корректно представляет массив байтов, должна иметь четное количество символов. 0 нигде не должен быть добавлен - для добавления было бы сделано предположение о недопустимых данных, которые потенциально опасны. Во всяком случае, метод StringToByteArray должен генерировать исключение FormatException, если шестнадцатеричная строка содержит нечетное количество символов.
Дэвид Бойк
7
@ 00jt Вы должны сделать предположение, что F == 0F. Либо он совпадает с 0F, либо вход был обрезан, и F фактически является началом чего-то, что вы не получили. Это зависит от вашего контекста, чтобы сделать эти предположения, но я считаю, что функция общего назначения должна отклонять нечетные символы как недействительные, вместо того, чтобы делать это предположение для вызывающего кода.
Дэвид Бойк
11
@DavidBoike Вопрос не имел никакого отношения к тому, «как обрабатывать возможные отсеченные значения потока». Это говорит о String. String myValue = 10.ToString ("X"); myValue это "A", а не "0A". Теперь прочитайте эту строку обратно в байты, ой, вы сломали ее.
00
488
Анализ производительности
Примечание: новый лидер с 2015-08-20.
Я проверил каждый из различных методов преобразования через некоторое грубое Stopwatchтестирование производительности, прогон со случайным предложением (n = 61, 1000 итераций) и прогон с текстом Project Gutenburg (n = 1 238 957, 150 итераций). Вот результаты, примерно от самого быстрого до самого медленного. Все измерения даны в тиках ( 10000 тиков = 1 мс ), и все относительные ноты сравниваются с [самой медленной] StringBuilderреализацией. Используемый код см. Ниже или в репозитории тестовой инфраструктуры, где я сейчас поддерживаю код для запуска этого.
отказ
ВНИМАНИЕ: не полагайтесь на эти характеристики для чего-то конкретного; это просто пример пробных данных. Если вам действительно нужна первоклассная производительность, протестируйте эти методы в среде, представляющей ваши производственные потребности, с данными, указывающими, что вы будете использовать.
Таблицы поиска взяли на себя инициативу по манипулированию байтами. По сути, существует некоторая форма предварительного вычисления того, какой любой данный клев или байт будет в шестнадцатеричном виде. Затем, просматривая данные, вы просто просматриваете следующую часть, чтобы увидеть, какой это будет шестнадцатеричная строка. Это значение затем добавляется к полученному выводу строки некоторым способом. Долгое время манипулирование байтами, потенциально трудное для чтения некоторыми разработчиками, было наиболее эффективным подходом.
Ваша лучшая ставка по-прежнему будет найти некоторые репрезентативные данные и опробовать их в производственной среде. Если у вас разные ограничения памяти, вы можете предпочесть метод с меньшим количеством выделений, чем метод, который был бы быстрее, но потреблял бы больше памяти.
Код тестирования
Не стесняйтесь играть с кодом тестирования, который я использовал. Версия включена здесь, но не стесняйтесь клонировать репо и добавлять свои собственные методы. Пожалуйста, отправьте запрос на удаление, если вы найдете что-нибудь интересное или хотите помочь улучшить используемую им инфраструктуру тестирования.
Добавьте новый статический метод ( Func<byte[], string>) в /Tests/ConvertByteArrayToHexString/Test.cs.
Добавьте имя этого метода к TestCandidatesвозвращаемому значению в том же классе.
Убедитесь, что вы используете нужную вам версию ввода, предложение или текст, переключая комментарии GenerateTestInputв том же классе.
Нажмите F5и дождитесь вывода (дамп HTML также генерируется в папке / bin).
staticstringByteArrayToHexStringViaStringJoinArrayConvertAll(byte[] bytes){returnstring.Join(string.Empty,Array.ConvertAll(bytes, b => b.ToString("X2")));}staticstringByteArrayToHexStringViaStringConcatArrayConvertAll(byte[] bytes){returnstring.Concat(Array.ConvertAll(bytes, b => b.ToString("X2")));}staticstringByteArrayToHexStringViaBitConverter(byte[] bytes){string hex =BitConverter.ToString(bytes);return hex.Replace("-","");}staticstringByteArrayToHexStringViaStringBuilderAggregateByteToString(byte[] bytes){return bytes.Aggregate(newStringBuilder(bytes.Length*2),(sb, b)=> sb.Append(b.ToString("X2"))).ToString();}staticstringByteArrayToHexStringViaStringBuilderForEachByteToString(byte[] bytes){StringBuilder hex =newStringBuilder(bytes.Length*2);foreach(byte b in bytes)
hex.Append(b.ToString("X2"));return hex.ToString();}staticstringByteArrayToHexStringViaStringBuilderAggregateAppendFormat(byte[] bytes){return bytes.Aggregate(newStringBuilder(bytes.Length*2),(sb, b)=> sb.AppendFormat("{0:X2}", b)).ToString();}staticstringByteArrayToHexStringViaStringBuilderForEachAppendFormat(byte[] bytes){StringBuilder hex =newStringBuilder(bytes.Length*2);foreach(byte b in bytes)
hex.AppendFormat("{0:X2}", b);return hex.ToString();}staticstringByteArrayToHexViaByteManipulation(byte[] bytes){char[] c =newchar[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);}returnnewstring(c);}staticstringByteArrayToHexViaByteManipulation2(byte[] bytes){char[] c =newchar[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));}returnnewstring(c);}staticstringByteArrayToHexViaSoapHexBinary(byte[] bytes){SoapHexBinary soapHexBinary =newSoapHexBinary(bytes);return soapHexBinary.ToString();}staticstringByteArrayToHexViaLookupAndShift(byte[] bytes){StringBuilder result =newStringBuilder(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();}staticreadonlyuint* _lookup32UnsafeP =(uint*)GCHandle.Alloc(_Lookup32,GCHandleType.Pinned).AddrOfPinnedObject();staticstringByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes){var lookupP = _lookup32UnsafeP;var result =newstring((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;}staticuint[]_Lookup32=Enumerable.Range(0,255).Select(i =>{string s = i.ToString("X2");return((uint)s[0])+((uint)s[1]<<16);}).ToArray();staticstringByteArrayToHexViaLookupPerByte(byte[] bytes){var result =newchar[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);}returnnewstring(result);}staticstringByteArrayToHexViaLookup(byte[] bytes){string[] hexStringTable =newstring[]{"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 =newStringBuilder(bytes.Length*2);foreach(byte b in bytes){
result.Append(hexStringTable[b]);}return result.ToString();}
Обновление (2010-01-13)
Добавлен ответ Валида на анализ. Довольно быстро.
Обновление (2011-10-05)
Добавлен string.ConcatArray.ConvertAllвариант для полноты (требуется .NET 4.0). Наравне с string.Joinверсией.
Обновление (2012-02-05)
Тест репо включает в себя больше вариантов, таких как StringBuilder.Append(b.ToString("X2")). Никто не расстроил результаты какие-либо. foreachбыстрее, чем {IEnumerable}.Aggregate, например, но BitConverterвсе же выигрывает.
Обновление (2012-04-03)
В SoapHexBinaryанализ добавлен ответ Майкрофта , который занял третье место.
Обновление (2013-01-15)
Добавлен ответ CodesInChaos по манипулированию байтами, который занял первое место (с большим запасом для больших блоков текста).
Обновление (2013-05-23)
Добавлен ответ поиска Натана Моинвазири и вариант из блога Брайана Ламберта. Оба довольно быстрые, но не идут впереди на тестовой машине, которую я использовал (AMD Phenom 9750).
Обновление (2014-07-31)
Добавлен новый байтовый ответ @ CodesInChaos. Похоже, что он взял на себя инициативу как по тестам предложений, так и по полнотекстовым тестам.
Обновление (2015-08-20)
Добавлены оптимизации и варианты airbreatherunsafe к репо этого ответа . Если вы хотите играть в небезопасную игру, вы можете получить огромный выигрыш в производительности по сравнению с любым из предыдущих лучших победителей как в коротких строках, так и в больших текстах.
Несмотря на то, что я сделал код доступным для вас, чтобы выполнить то, что вы сами запросили, я обновил код тестирования, включив в него ответ Waleed. Не говоря о раздражительности, это гораздо быстрее.
Патридж
2
@CodesInChaos Готово. И в моих тестах он тоже немного выиграл. Я не претендую на то, чтобы полностью понять ни один из лучших методов, но они легко скрыты от прямого взаимодействия.
Патридж
6
Этот ответ не намерен отвечать на вопрос о том, что является «естественным» или обычным явлением. Цель состоит в том, чтобы дать людям некоторые базовые показатели производительности, поскольку, когда вам нужно выполнить это преобразование, вы склонны делать их много. Если кому-то нужна грубая скорость, он просто запускает эталонные тесты с соответствующими тестовыми данными в нужной вычислительной среде. Затем уберите этот метод в метод расширения, где вы никогда больше не посмотрите на его реализацию (например, bytes.ToHexStringAtLudicrousSpeed()).
Патридж
2
Только что произвел реализацию на основе высокоэффективной таблицы поиска. Его безопасный вариант примерно на 30% быстрее, чем текущий лидер на моем процессоре. Небезопасные варианты еще быстрее. stackoverflow.com/a/24343727/445517
CodesInChaos
244
Есть класс с именем SoapHexBinary, который делает именно то, что вы хотите.
using System.Runtime.Remoting.Metadata.W3cXsd2001;publicstaticbyte[]GetStringToBytes(stringvalue){SoapHexBinary shb =SoapHexBinary.Parse(value);return shb.Value;}publicstaticstringGetBytesToString(byte[]value){SoapHexBinary shb =newSoapHexBinary(value);return shb.ToString();}
SoapHexBinary не поддерживается в .NET Ядра / .NET Standard ...
ЮФО
141
При написании крипто-кода обычно избегают зависимых от данных ветвей и поиска таблиц, чтобы гарантировать, что время выполнения не зависит от данных, поскольку зависящее от данных время может привести к атакам по побочным каналам.
Это также довольно быстро.
staticstringByteToHexBitFiddle(byte[] bytes){char[] c =newchar[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));}returnnewstring(c);}
b - 10
это < 0для значений b < 10, которые станут десятичной цифрой
является >= 0для значений b > 10, которые станут письмом от Aдо F.
Использование i >> 3132-разрядного целого числа со знаком извлекает знак благодаря расширению знака. Это будет -1для i < 0и 0для i >= 0.
Объединяя 2) и 3), показывает, что (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. Но так как -1 представлен всеми битами, равными 1, мы можем вместо этого использовать & -7с (0 & -7) == 0и (-1 & -7) == -7.
Некоторые дальнейшие соображения:
Я не использовал вторую переменную цикла для индексации c, так как измерения показывают, что вычислять ее iпо более низкой цене.
Использование именно i < bytes.Lengthверхней границы цикла позволяет JITter исключать проверки границ bytes[i], поэтому я выбрал этот вариант.
Создание bint позволяет ненужные преобразования от и до байта.
Еще короче: String.Concat (Array.ConvertAll (bytes, x => x.ToString ("X2"))
Нестор
14
Еще короче: String.Concat (bytes.Select (b => b.ToString ("X2"))) [.NET4]
Аллон Гуралнек
14
Только отвечает на половину вопроса.
Хитрый Грифон
1
Зачем второму нужен .Net 4? String.Concat находится в .Net 2.0.
Polyfun
2
эти петли "стиля 90-х", как правило, быстрее, но на ничтожно малое количество, что не имеет значения в большинстве случаев. Тем не менее, стоит упомянуть
Остин_Андерсон
69
Еще один подход на основе таблицы поиска. Этот использует только одну таблицу поиска для каждого байта вместо таблицы поиска для каждого куска.
privatestaticreadonlyuint[] _lookup32 =CreateLookup32();privatestaticuint[]CreateLookup32(){var result =newuint[256];for(int i =0; i <256; i++){string s=i.ToString("X2");
result[i]=((uint)s[0])+((uint)s[1]<<16);}return result;}privatestaticstringByteArrayToHexViaLookup32(byte[] bytes){var lookup32 = _lookup32;var result =newchar[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);}returnnewstring(result);}
Я также протестировал варианты этого использования ushort, struct{char X1, X2}, struct{byte X1, X2}в справочной таблице.
В зависимости от цели компиляции (x86, X64) они либо имели примерно одинаковую производительность, либо были немного медленнее, чем этот вариант.
И для еще более высокой производительности, его unsafeродной брат:
privatestaticreadonlyuint[] _lookup32Unsafe =CreateLookup32Unsafe();privatestaticreadonlyuint* _lookup32UnsafeP =(uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();privatestaticuint[]CreateLookup32Unsafe(){var result =newuint[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;}publicstaticstringByteArrayToHexViaLookup32Unsafe(byte[] bytes){var lookupP = _lookup32UnsafeP;var result =newchar[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]];}}returnnewstring(result);}
Или, если вы считаете приемлемым писать в строку напрямую:
publicstaticstringByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes){var lookupP = _lookup32UnsafeP;var result =newstring((char)0, bytes.Length*2);fixed(byte* bytesP = bytes)fixed(char* resultP = result){uint* resultP2 =(uint*)resultP;for(int i =0; i < bytes.Length; i++){
resultP2[i]= lookupP[bytesP[i]];}}return result;}
Почему при создании таблицы поиска в небезопасной версии меняются кусочки предварительно вычисленного байта? Я думал, что порядок байтов только изменил порядок объектов, которые были сформированы из нескольких байтов.
Раиф Атеф
@RaifAtef Здесь важен не порядок грызунов. Но порядка 16 битных слов в 32-битном целом числе. Но я подумываю переписать его, чтобы один и тот же код мог работать независимо от порядка байтов.
CodesInChaos
Перечитывая код, я думаю, что вы сделали это, потому что, когда вы приводите char * позже к uint * и назначаете его (при генерации шестнадцатеричного char), среда выполнения / CPU переворачивают байты (поскольку uint не обрабатывается так же, как 2 отдельных 16-битных символа), так что вы предварительно переворачиваете их для компенсации. Я прав ? Endianness сбивает с толку :-).
Раиф Атеф
4
Это просто ответ на половину вопроса ... Как насчет шестнадцатеричной строки в байтах?
Narvalex
3
@ CodesInChaos Интересно, Spanможно ли использовать сейчас вместо unsafe??
Конрад
64
Вы можете использовать метод BitConverter.ToString:
Я только что столкнулся с той же проблемой сегодня, и я наткнулся на этот код:
privatestaticstringByteArrayToHex(byte[] barray){char[] c =newchar[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);}returnnewstring(c);}
Источник: сообщение на форуме byte [] Array to Hex String (см. Сообщение PZahra). Я немного изменил код, чтобы удалить префикс 0x.
Я провел некоторое тестирование производительности в коде, и это было почти в восемь раз быстрее, чем при использовании BitConverter.ToString () (самый быстрый согласно сообщению Патриджа).
не говоря уже о том, что для этого используется меньше всего памяти. Никаких промежуточных строк не создано.
Chochos
8
Только отвечает на половину вопроса.
Хитрый Грифон
Это здорово, потому что работает практически на любой версии NET, включая NETMF. Победитель!
Jonesome Восстановить Монику
1
Принятый ответ предоставляет 2 превосходных метода HexToByteArray, которые представляют другую половину вопроса. Решение Waleed отвечает на вопрос о том, как это сделать, не создавая огромное количество строк в процессе.
Брендтен Эйкштадт
Копирует и перераспределяет ли новая строка (c) или она достаточно умна, чтобы знать, когда она может просто обернуть символ []?
Я сделаю так, что это редактирование неверно, и объясню, почему оно может быть отменено. Попутно вы можете узнать кое-что о некоторых внутренних элементах и увидеть еще один пример того, что такое преждевременная оптимизация и как она может вас укусить.
tl; dr: Просто используйте, Convert.ToByteи String.Substringесли вы спешите («Оригинальный код» ниже), то это лучшая комбинация, если вы не хотите повторно внедрять Convert.ToByte. Используйте что-то более продвинутое (см. Другие ответы), которое не используется, Convert.ToByteесли вам нужна производительность. Есть не что - нибудь еще другое использование , чем String.Substringв сочетании с Convert.ToByte, если кто - то имеет что - то интересное , чтобы сказать об этом в комментариях этого ответа.
предупреждение: Ответ на этот вопрос может устареть , еслиConvert.ToByte(char[], Int32) перегрузка осуществляется в рамках. Это вряд ли произойдет в ближайшее время.
Как правило, я не очень люблю говорить «не оптимизировать преждевременно», потому что никто не знает, когда «преждевременно». Единственное, что вы должны учитывать при принятии решения об оптимизации или нет, это: «У меня есть время и ресурсы для правильного исследования подходов к оптимизации?». Если вы этого не сделаете, то это слишком рано, подождите , пока ваш проект не будет более зрелым или до тех пор , пока нужна производительность (если есть реальная необходимость, то вы будете делать время). А пока сделайте самое простое, что могло бы сработать.
Оригинальный код:
publicstaticbyte[]HexadecimalStringToByteArray_Original(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];for(var i =0; i < outputLength; i++)
output[i]=Convert.ToByte(input.Substring(i *2,2),16);return output;}
Редакция 4:
publicstaticbyte[]HexadecimalStringToByteArray_Rev4(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];
using (var sr =newStringReader(input)){for(var i =0; i < outputLength; i++)
output[i]=Convert.ToByte(newstring(newchar[2]{(char)sr.Read(),(char)sr.Read()}),16);}return output;}
Редакция избегает String.Substringи используетStringReader вместо этого. Данная причина:
Изменить: вы можете улучшить производительность для длинных строк с помощью однопроходного парсера, например так:
Ну, глядя на код ссылки дляString.Substring , он уже явно "однопроходный"; а почему не должно быть? Он работает на уровне байтов, а не на суррогатных парах.
Тем не менее, он выделяет новую строку, но тогда вам нужно выделить ее для передачи в Convert.ToByteлюбом случае. Кроме того, решение, представленное в ревизии, выделяет еще один объект на каждой итерации (массив из двух символов); Вы можете безопасно поместить это распределение за пределы цикла и повторно использовать массив, чтобы избежать этого.
publicstaticbyte[]HexadecimalStringToByteArray(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];var numeral =newchar[2];
using (var sr =newStringReader(input)){for(var i =0; i < outputLength; i++){
numeral[0]=(char)sr.Read();
numeral[1]=(char)sr.Read();
output[i]=Convert.ToByte(newstring(numeral),16);}}return output;}
Каждый шестнадцатеричный numeralпредставляет один октет с использованием двух цифр (символов).
Но тогда зачем звонить StringReader.Readдважды? Просто вызовите его вторую перегрузку и попросите его прочитать сразу два символа в массиве из двух символов; и уменьшить количество звонков на два.
publicstaticbyte[]HexadecimalStringToByteArray(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];var numeral =newchar[2];
using (var sr =newStringReader(input)){for(var i =0; i < outputLength; i++){var read = sr.Read(numeral,0,2);Debug.Assert(read ==2);
output[i]=Convert.ToByte(newstring(numeral),16);}}return output;}
То, что у вас осталось, это средство чтения строк, единственным добавленным «значением» которого является параллельный индекс (внутренний _pos), который вы могли бы объявить самостоятельно (как, jнапример), избыточная переменная длины (внутренняя _length) и избыточная ссылка на вход строка (внутренняя_s ). Другими словами, это бесполезно.
Если вам интересно, как Read«читается», просто посмотрите на код , все, что он делает, это вызывает String.CopyToвходную строку. Все остальное - это просто расходы на ведение бухгалтерского учета для поддержания ценностей, которые нам не нужны.
Итак, удалите читатель строки и позвоните CopyToсебе; это проще, понятнее и эффективнее.
Вам действительно нужен jиндекс, который увеличивается с шагом в две параллели i? Конечно, нет, просто умножьте iна два (что компилятор должен уметь оптимизировать до сложения).
publicstaticbyte[]HexadecimalStringToByteArray_BestEffort(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];var numeral =newchar[2];for(int i =0; i < outputLength; i++){
input.CopyTo(i *2, numeral,0,2);
output[i]=Convert.ToByte(newstring(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.60GHzCores:8CurrentClockSpeed:2600MaxClockSpeed:2600--------------------Parsing hexadecimal stringinto an array of bytes
--------------------HexadecimalStringToByteArray_Original:7,777.09 average ticks (over 10000 runs),1.2XHexadecimalStringToByteArray_BestEffort:8,550.82 average ticks (over 10000 runs),1.1XHexadecimalStringToByteArray_Rev4:9,218.03 average ticks (over 10000 runs),1.0X
Да!
Реквизит в Partridge для каркаса скамейки, его легко взломать. В качестве входных данных используется следующий хэш SHA-1, повторенный 5000 раз, чтобы получить строку длиной 100 000 байт.
209113288F93A9AB8E474EA78D899AFDBB874355
Радоваться, веселиться! (Но оптимизируйте с модерацией.)
ошибка: {"Не удалось найти какие-либо узнаваемые цифры."}
Прия Джагтап
17
Дополнение к ответу @CodesInChaos (обратный метод)
publicstaticbyte[]HexToByteUsingByteManipulation(string s){byte[] bytes =newbyte[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...).
Эту проблему также можно решить с помощью справочной таблицы. Это потребует небольшого количества статической памяти как для кодера, так и для декодера. Однако этот метод будет быстрым:
Таблица кодировщика 512 байт или 1024 байт (в два раза больше, если требуется прописная и строчная буквы)
Таблица декодера 256 байтов или 64 КиБ (либо поиск одного символа, либо поиск двух символов)
Мое решение использует 1024 байта для таблицы кодирования и 256 байтов для декодирования.
Декодирование
privatestaticreadonlybyte[]LookupTable=newbyte[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,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};privatestaticbyteLookup(char c){var b =LookupTable[c];if(b ==255)thrownewIOException("Expected a hex character, got "+ c);return b;}publicstaticbyteToByte(char[] chars,int offset){return(byte)(Lookup(chars[offset])<<4|Lookup(chars[offset +1]));}
кодирование
privatestaticreadonlychar[][]LookupTableUpper;privatestaticreadonlychar[][]LookupTableLower;staticHex(){LookupTableLower=newchar[256][];LookupTableUpper=newchar[256][];for(var i =0; i <256; i++){LookupTableLower[i]= i.ToString("x2").ToCharArray();LookupTableUpper[i]= i.ToString("X2").ToCharArray();}}publicstaticchar[]ToCharLower(byte[] b,int bOffset){returnLookupTableLower[b[bOffset]];}publicstaticchar[]ToCharUpper(byte[] b,int bOffset){returnLookupTableUpper[b[bOffset]];}
Во время декодирования могут возникнуть IOException и IndexOutOfRangeException (если символ имеет слишком высокое значение> 256). Методы для де / кодирования потоков или массивов должны быть реализованы, это просто подтверждение концепции.
Использование памяти в 256 байт незначительно при запуске кода в CLR.
дольмен
9
Это отличная статья. Мне нравится решение Валида. Я не прошел тест Патриджа, но, похоже, он проходит довольно быстро. Мне также потребовался обратный процесс, преобразование шестнадцатеричной строки в байтовый массив, поэтому я написал ее как обращение решения Валида. Не уверен, что это быстрее, чем оригинальное решение Томалака. Опять же, я не запускал обратный процесс через тест Патриджа.
privatebyte[]HexStringToByteArray(string hexString){int hexStringLength = hexString.Length;byte[] b =newbyte[hexStringLength /2];for(int i =0; i < hexStringLength; i +=2){int topChar =(hexString[i]>0x40? hexString[i]-0x37: hexString[i]-0x30)<<4;int bottomChar = hexString[i +1]>0x40? hexString[i +1]-0x37: hexString[i +1]-0x30;
b[i /2]=Convert.ToByte(topChar + bottomChar);}return b;}
В этом коде предполагается, что в шестнадцатеричной строке используются буквенные символы в верхнем регистре, и разрывается, если в шестнадцатеричной строке используется буквенный код в нижнем регистре. Возможно, вы захотите сделать преобразование в верхнем регистре для входной строки, чтобы быть в безопасности.
Марк Новаковски
Это проницательное наблюдение, Марк. Код был написан, чтобы полностью изменить решение Валида. Вызов ToUpper несколько замедлит алгоритм, но позволит обрабатывать строчные буквенные символы.
Крис Ф
3
Convert.ToByte (topChar + bottomChar) можно записать как (байт) (topChar + bottomChar)
Амир Резаи
Чтобы обрабатывать оба случая без большого штрафа за производительность,hexString[i] &= ~0x20;
Бен Фойгт
9
Зачем делать это сложным? Это просто в Visual Studio 2008:
причина в производительности, когда вам нужно высокопроизводительное решение. :)
Рикки
7
Чтобы не отвечать на многие ответы здесь, я нашел довольно оптимальную (~ 4,5 раза лучше, чем принято) прямую реализацию синтаксического анализатора шестнадцатеричных строк. Сначала вывод моих тестов (первая партия - моя реализация):
Give me that string:04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939fTime to parse 100,000 times:50.4192 ms
Resultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9FAccepted answer:(StringToByteArray)Time to parse 100000 times:233.1264msResultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9FWithMono's implementation:Time to parse 100000 times:777.2544msResultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9FWithSoapHexBinary:Time to parse 100000 times:845.1456msResultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9F
Строки base64 и BitConverter'd проверяют правильность. Обратите внимание, что они равны.
Реализация:
publicstaticbyte[]ToByteArrayFromHex(string hexString){if(hexString.Length%2!=0)thrownewArgumentException("String must have an even length");vararray=newbyte[hexString.Length/2];for(int i =0; i < hexString.Length; i +=2){array[i/2]=ByteFromTwoChars(hexString[i], hexString[i +1]);}returnarray;}privatestaticbyteByteFromTwoChars(char p,char p_2){byte ret;if(p <='9'&& p >='0'){
ret =(byte)((p -'0')<<4);}elseif(p <='f'&& p >='a'){
ret =(byte)((p -'a'+10)<<4);}elseif(p <='F'&& p >='A'){
ret =(byte)((p -'A'+10)<<4);}elsethrownewArgumentException("Char is not a hex digit: "+ p,"p");if(p_2 <='9'&& p_2 >='0'){
ret |=(byte)((p_2 -'0'));}elseif(p_2 <='f'&& p_2 >='a'){
ret |=(byte)((p_2 -'a'+10));}elseif(p_2 <='F'&& p_2 >='A'){
ret |=(byte)((p_2 -'A'+10));}elsethrownewArgumentException("Char is not a hex digit: "+ p_2,"p_2");return ret;}
Я попробовал кое-что с unsafeи переместил (явно избыточную) ifпоследовательность символов в клев на другой метод, но это было быстрее всего.
(Я допускаю, что это отвечает на половину вопроса. Я чувствовал, что преобразование string-> byte [] было недопредставлено, в то время как угол строки byte [] ->, кажется, хорошо покрыт. Таким образом, этот ответ.)
Для последователей Кнута: я сделал это, потому что мне нужно анализировать несколько тысяч шестнадцатеричных строк каждые несколько минут или около того, поэтому важно, чтобы это было как можно быстрее (как бы во внутреннем цикле). Решение Томалака не заметно медленнее, если много таких разборов не происходит.
Бен Мошер
5
Безопасные версии:
publicstaticclassHexHelper{[System.Diagnostics.Contracts.Pure]publicstaticstringToHex(thisbyte[]value){if(value==null)thrownewArgumentNullException("value");conststring hexAlphabet =@"0123456789ABCDEF";var chars =newchar[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];}}returnnewstring(chars);}[System.Diagnostics.Contracts.Pure]publicstaticbyte[]FromHex(thisstringvalue){if(value==null)thrownewArgumentNullException("value");if(value.Length%2!=0)thrownewArgumentException("Hexadecimal value length must be even.","value");unchecked{byte[] result =newbyte[value.Length/2];for(int i =0; i < result.Length; i++){// 0(48) - 9(57) -> 0 - 9// A(65) - F(70) -> 10 - 15int 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.
publicstaticclassHexUnsafeHelper{[System.Diagnostics.Contracts.Pure]publicstaticunsafestringToHex(thisbyte[]value){if(value==null)thrownewArgumentNullException("value");conststring alphabet =@"0123456789ABCDEF";string result =newstring(' ',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]publicstaticunsafebyte[]FromHex(thisstringvalue){if(value==null)thrownewArgumentNullException("value");if(value.Length%2!=0)thrownewArgumentException("Hexadecimal value length must be even.","value");unchecked{byte[] result =newbyte[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 - 15int 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):
publicstaticbyte[]HexToBytes(thisstring hexString){byte[] b =newbyte[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 с поддержкой нижнего регистра:
publicstaticstringBytesToHex(thisbyte[] barray,bool toLowerCase =true){byte addByte =0x37;if(toLowerCase) addByte =0x57;char[] c =newchar[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);}returnnewstring(c);}
Методы расширения (отказ от ответственности: полностью непроверенный код, кстати ...):
publicstaticclassByteExtensions{publicstaticstringToHexString(thisbyte[] ba){StringBuilder hex =newStringBuilder(ba.Length*2);foreach(byte b in ba){
hex.AppendFormat("{0:x2}", b);}return hex.ToString();}}
и т. д. Используйте одно из трех решений Томалака (последнее из которых является методом расширения строки).
Вам, вероятно, следует протестировать код, прежде чем предлагать его для такого вопроса.
jww
3
От разработчиков Microsoft, хорошее, простое преобразование:
publicstaticstringByteArrayToString(byte[] ba){// Concatenate the bytes into one long stringreturn ba.Aggregate(newStringBuilder(32),(sb, b)=> sb.Append(b.ToString("X2"))).ToString();}
Хотя вышесказанное чисто и компактно, любители производительности будут кричать об этом, используя счетчики. Вы можете добиться максимальной производительности с улучшенной версией оригинального ответа Томалака :
publicstaticstringByteArrayToString(byte[] ba){StringBuilder hex =newStringBuilder(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 для себя.
если Source == nullили Source.Length == 0у нас есть проблемы, сэр!
Андрей Красуцкий
2
С точки зрения скорости это кажется лучше, чем что-либо здесь:
publicstaticstringToHexString(byte[] data){byte b;int i, j, k;int l = data.Length;char[] r =newchar[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);}returnnewstring(r);}
Я не получил код, который ты предложил работать, Олипро. hex[i] + hex[i+1]видимо вернул int.
Однако я добился определенного успеха, взяв несколько подсказок из кода Waleeds и собрав их вместе. Это ужасно ужасно, но, кажется, работает и работает в 1/3 времени по сравнению с другими, согласно моим тестам (с использованием механизма тестирования патронов). В зависимости от размера ввода. Переключение вокруг?: S для выделения 0-9 первым, вероятно, приведет к несколько более быстрому результату, поскольку в нем больше цифр, чем букв.
publicstaticbyte[]StringToByteArray2(string hex){byte[] bytes =newbyte[hex.Length/2];int bl = bytes.Length;for(int i =0; i < bl;++i){
bytes[i]=(byte)((hex[2* i]>'F'? hex[2* i]-0x57: hex[2* i]>'9'? hex[2* i]-0x37: hex[2* i]-0x30)<<4);
bytes[i]|=(byte)(hex[2* i +1]>'F'? hex[2* i +1]-0x57: hex[2* i +1]>'9'? hex[2* i +1]-0x37: hex[2* i +1]-0x30);}return bytes;}
Эта версия ByteArrayToHexViaByteManipulation может быть быстрее.
Из моих отчетов:
ByteArrayToHexViaByteManipulation3: 1,68 средних тиков (более 1000 пробежек), 17,5X
ByteArrayToHexViaByteManipulation2: 1,73 средних тиков (более 1000 пробежек), 16,9X
ByteArrayToHexViaByteManipulation: 2,90 средних тиков (более 1000 прогонов), 10,1X
ByteArrayToHexViaLookupAndShift: 3,22 средних тиков (более 1000 прогонов), 9,1X
...
staticprivatereadonlychar[] hexAlphabet =newchar[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};staticstringByteArrayToHexViaByteManipulation3(byte[] bytes){char[] c =newchar[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];}returnnewstring(c);}
И я думаю, что это оптимизация:
staticprivatereadonlychar[] hexAlphabet =newchar[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};staticstringByteArrayToHexViaByteManipulation4(byte[] bytes){char[] c =newchar[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];}returnnewstring(c);}
Я приму участие в этом соревновании по битрейту, так как у меня есть ответ, который также использует битовую скрипку для декодирования шестнадцатеричных чисел. Обратите внимание, что использование символьных массивов может быть еще быстрее, так как вызов StringBuilderметодов также займет время.
publicstaticStringToHex(byte[] data){int dataLength = data.Length;// pre-create the stringbuilder using the length of the data * 2, precisely enoughStringBuilder sb =newStringBuilder(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 letterint 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 characterint 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();}publicstaticbyte[]FromHex(String hex){// pre-create the arrayint resultLength = hex.Length/2;byte[] result =newbyte[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 basedvalue=((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){thrownewArgumentException("Hexadecimal encoding incorrect for input "+ hex);}return result;}
Хм, я действительно должен оптимизировать это для Char[]и использовать Charвнутренне вместо целых ...
Maarten Bodewes
Для C # инициализация переменных, где они используются, а не вне цикла, вероятно, предпочтительнее, чтобы позволить компилятору оптимизировать. Я получаю эквивалентную производительность в любом случае.
Петер
2
Для производительности я бы пошел с раствором drphrozens. Небольшой оптимизацией для декодера может быть использование таблицы для любого символа, чтобы избавиться от «<< 4».
Очевидно, что два вызова метода являются дорогостоящими. Если какая-либо проверка выполняется на входных или выходных данных (может быть CRC, контрольная сумма или что-то еще),if (b == 255)... можно пропустить и, таким образом, также вызвать метод в целом.
Использование offset++и offsetвместо offsetи offset + 1может дать некоторое теоретическое преимущество, но я подозреваю, что компилятор справляется с этим лучше, чем я.
privatestaticreadonlybyte[]LookupTableLow=newbyte[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,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};privatestaticreadonlybyte[]LookupTableHigh=newbyte[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,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};privatestaticbyteLookupLow(char c){var b =LookupTableLow[c];if(b ==255)thrownewIOException("Expected a hex character, got "+ c);return b;}privatestaticbyteLookupHigh(char c){var b =LookupTableHigh[c];if(b ==255)thrownewIOException("Expected a hex character, got "+ c);return b;}publicstaticbyteToByte(char[] chars,int offset){return(byte)(LookupHigh(chars[offset++])|LookupLow(chars[offset]));}
Это только с моей головы и не было проверено или сравнительно.
publicstaticbyte[]FromHexString(string src){if(String.IsNullOrEmpty(src))returnnull;int index = src.Length;int sz = index /2;if(sz <=0)returnnull;byte[] rc =newbyte[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;}
publicstaticstringByteArrayToString2(byte[] ba){char[] c =newchar[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));}returnnewstring( c );}
Декадентская версия linq-with-bit-hacking:
publicstaticstringByteArrayToString(byte[] ba){returnstring.Concat( ba.SelectMany( b =>newint[]{ b >>4, b &0xF}).Select( b =>(char)(55+ b +(((b-10)>>31)&-7))));}
И наоборот:
publicstaticbyte[]HexStringToByteArray(string s ){byte[] ab =newbyte[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;}
HexStringToByteArray ("09") возвращает 0x02, что плохо
CoperNick
1
Другой способ - использовать stackallocдля уменьшения давления памяти GC:
staticstringByteToHexBitFiddle(byte[] bytes){var c =stackallocchar[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';returnnewstring(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
Исходя из приведенных выше результатов, можно с уверенностью заключить, что:
Штрафы за индексирование в строку для выполнения поиска по сравнению с массивом символов значительны в тесте большого файла.
Штрафы за использование StringBuilder известной емкости по сравнению с массивом символов известного размера для создания строки еще более значительны.
Вот код:
using System;
namespace ConversionExtensions{publicstaticclassByteArrayExtensions{privatereadonlystaticchar[] digits =newchar[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};publicstaticstringToHexString(thisbyte[] bytes){char[] hex =newchar[bytes.Length*2];int index =0;foreach(byte b in bytes){
hex[index++]= digits[b >>4];
hex[index++]= digits[b &0x0F];}returnnewstring(hex);}}}
using System;
using System.IO;
namespace ConversionExtensions{publicstaticclassStringExtensions{publicstaticbyte[]ToBytes(thisstring hexString){if(!string.IsNullOrEmpty(hexString)&& hexString.Length%2!=0){thrownewFormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");}
hexString = hexString.ToUpperInvariant();byte[] data =newbyte[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){thrownewFormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");}else{bytevalue=(byte)((highDigitValue <<4)|(lowDigitValue &0x0F));
data[index /2]=value;}}return data;}}}
Ниже приведены результаты тестирования, которые я получил, когда поместил свой код в проект тестирования @ patridge на моей машине. Я также добавил тест для преобразования байтового массива из шестнадцатеричного. Тестовые прогоны, которые выполняли мой код: ByteArrayToHexViaOptimizedLookupAndShift и HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte был взят из XXXX. HexToByteArrayViaSoapHexBinary - это ответ @ Mykroft.
Ответы:
Или:
или:
Есть еще больше вариантов сделать это, например, здесь .
Обратное преобразование будет выглядеть так:
Использование
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
в том же классе.Обновление (2010-01-13)
Добавлен ответ Валида на анализ. Довольно быстро.
Обновление (2011-10-05)
Добавлен
string.Concat
Array.ConvertAll
вариант для полноты (требуется .NET 4.0). Наравне сstring.Join
версией.Обновление (2012-02-05)
Тест репо включает в себя больше вариантов, таких как
StringBuilder.Append(b.ToString("X2"))
. Никто не расстроил результаты какие-либо.foreach
быстрее, чем{IEnumerable}.Aggregate
, например, ноBitConverter
все же выигрывает.Обновление (2012-04-03)
В
SoapHexBinary
анализ добавлен ответ Майкрофта , который занял третье место.Обновление (2013-01-15)
Добавлен ответ CodesInChaos по манипулированию байтами, который занял первое место (с большим запасом для больших блоков текста).
Обновление (2013-05-23)
Добавлен ответ поиска Натана Моинвазири и вариант из блога Брайана Ламберта. Оба довольно быстрые, но не идут впереди на тестовой машине, которую я использовал (AMD Phenom 9750).
Обновление (2014-07-31)
Добавлен новый байтовый ответ @ CodesInChaos. Похоже, что он взял на себя инициативу как по тестам предложений, так и по полнотекстовым тестам.
Обновление (2015-08-20)
Добавлены оптимизации и варианты airbreather
unsafe
к репо этого ответа . Если вы хотите играть в небезопасную игру, вы можете получить огромный выигрыш в производительности по сравнению с любым из предыдущих лучших победителей как в коротких строках, так и в больших текстах.источник
bytes.ToHexStringAtLudicrousSpeed()
).Есть класс с именем SoapHexBinary, который делает именно то, что вы хотите.
источник
При написании крипто-кода обычно избегают зависимых от данных ветвей и поиска таблиц, чтобы гарантировать, что время выполнения не зависит от данных, поскольку зависящее от данных время может привести к атакам по побочным каналам.
Это также довольно быстро.
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. Но так как -1 представлен всеми битами, равными 1, мы можем вместо этого использовать
& -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-х годов, но вы не можете:Или, если вы используете .NET 4.0:
(Последнее из комментария к оригинальному сообщению.)
источник
Еще один подход на основе таблицы поиска. Этот использует только одну таблицу поиска для каждого байта вместо таблицы поиска для каждого куска.
Я также протестировал варианты этого использования
ushort
,struct{char X1, X2}
,struct{byte X1, X2}
в справочной таблице.В зависимости от цели компиляции (x86, X64) они либо имели примерно одинаковую производительность, либо были немного медленнее, чем этот вариант.
И для еще более высокой производительности, его
unsafe
родной брат:Или, если вы считаете приемлемым писать в строку напрямую:
источник
Span
можно ли использовать сейчас вместоunsafe
??Вы можете использовать метод BitConverter.ToString:
Вывод:
Дополнительная информация: метод BitConverter.ToString (Byte [])
источник
Я только что столкнулся с той же проблемой сегодня, и я наткнулся на этот код:
Источник: сообщение на форуме 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)
перегрузка осуществляется в рамках. Это вряд ли произойдет в ближайшее время.Как правило, я не очень люблю говорить «не оптимизировать преждевременно», потому что никто не знает, когда «преждевременно». Единственное, что вы должны учитывать при принятии решения об оптимизации или нет, это: «У меня есть время и ресурсы для правильного исследования подходов к оптимизации?». Если вы этого не сделаете, то это слишком рано, подождите , пока ваш проект не будет более зрелым или до тех пор , пока нужна производительность (если есть реальная необходимость, то вы будете делать время). А пока сделайте самое простое, что могло бы сработать.
Оригинальный код:
Редакция 4:
Редакция избегает
String.Substring
и используетStringReader
вместо этого. Данная причина:Ну, глядя на код ссылки для
String.Substring
, он уже явно "однопроходный"; а почему не должно быть? Он работает на уровне байтов, а не на суррогатных парах.Тем не менее, он выделяет новую строку, но тогда вам нужно выделить ее для передачи в
Convert.ToByte
любом случае. Кроме того, решение, представленное в ревизии, выделяет еще один объект на каждой итерации (массив из двух символов); Вы можете безопасно поместить это распределение за пределы цикла и повторно использовать массив, чтобы избежать этого.Каждый шестнадцатеричный
numeral
представляет один октет с использованием двух цифр (символов).Но тогда зачем звонить
StringReader.Read
дважды? Просто вызовите его вторую перегрузку и попросите его прочитать сразу два символа в массиве из двух символов; и уменьшить количество звонков на два.То, что у вас осталось, это средство чтения строк, единственным добавленным «значением» которого является параллельный индекс (внутренний
_pos
), который вы могли бы объявить самостоятельно (как,j
например), избыточная переменная длины (внутренняя_length
) и избыточная ссылка на вход строка (внутренняя_s
). Другими словами, это бесполезно.Если вам интересно, как
Read
«читается», просто посмотрите на код , все, что он делает, это вызываетString.CopyTo
входную строку. Все остальное - это просто расходы на ведение бухгалтерского учета для поддержания ценностей, которые нам не нужны.Итак, удалите читатель строки и позвоните
CopyTo
себе; это проще, понятнее и эффективнее.Вам действительно нужен
j
индекс, который увеличивается с шагом в две параллелиi
? Конечно, нет, просто умножьтеi
на два (что компилятор должен уметь оптимизировать до сложения).Как выглядит решение сейчас? Точно так же, как это было в начале, только вместо того,
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)
, что вы действительно должны делать, если вам все равно нужна производительность. Посмотрите на бесчисленное множество других ответов, чтобы обнаружить все различные подходы для этого.Отказ от ответственности: я не декомпилировал последнюю версию фреймворка, чтобы убедиться, что справочный источник обновлен, я предполагаю, что это так.
Теперь все это звучит хорошо и логично, надеюсь, даже очевидно, если вам удалось продвинуться так далеко. Но так ли это?
Да!
Реквизит в Partridge для каркаса скамейки, его легко взломать. В качестве входных данных используется следующий хэш SHA-1, повторенный 5000 раз, чтобы получить строку длиной 100 000 байт.
Радоваться, веселиться! (Но оптимизируйте с модерацией.)
источник
Дополнение к ответу @CodesInChaos (обратный метод)
Объяснение:
& 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 байтов для декодирования.
Декодирование
кодирование
сравнение
* это решение
Запись
Во время декодирования могут возникнуть IOException и IndexOutOfRangeException (если символ имеет слишком высокое значение> 256). Методы для де / кодирования потоков или массивов должны быть реализованы, это просто подтверждение концепции.
источник
Это отличная статья. Мне нравится решение Валида. Я не прошел тест Патриджа, но, похоже, он проходит довольно быстро. Мне также потребовался обратный процесс, преобразование шестнадцатеричной строки в байтовый массив, поэтому я написал ее как обращение решения Валида. Не уверен, что это быстрее, чем оригинальное решение Томалака. Опять же, я не запускал обратный процесс через тест Патриджа.
источник
hexString[i] &= ~0x20;
Зачем делать это сложным? Это просто в Visual Studio 2008:
C #:
VB:
источник
Чтобы не отвечать на многие ответы здесь, я нашел довольно оптимальную (~ 4,5 раза лучше, чем принято) прямую реализацию синтаксического анализатора шестнадцатеричных строк. Сначала вывод моих тестов (первая партия - моя реализация):
Строки base64 и BitConverter'd проверяют правильность. Обратите внимание, что они равны.
Реализация:
Я попробовал кое-что с
unsafe
и переместил (явно избыточную)if
последовательность символов в клев на другой метод, но это было быстрее всего.(Я допускаю, что это отвечает на половину вопроса. Я чувствовал, что преобразование string-> byte [] было недопредставлено, в то время как угол строки byte [] ->, кажется, хорошо покрыт. Таким образом, этот ответ.)
источник
Безопасные версии:
Небезопасные версии Для тех, кто предпочитает производительность и не боится небезопасности. На 35% быстрее ToHex и на 10% быстрее FromHex.
КСТАТИ для тестирования производительности инициализация алфавита каждый раз, когда вызываемая функция преобразования неверна, алфавит должен быть const (для строки) или статическим readonly (для char []). Затем основанное на алфавите преобразование byte [] в строку становится таким же быстрым, как версии манипулирования байтами.
И, конечно, тест должен быть скомпилирован в Release (с оптимизацией) и с отключенной опцией «Отключить оптимизацию JIT» (то же самое для «Enable Just My Code», если код должен быть отлаживаемым).
источник
Обратная функция для кода Валида Эйссы (Hex String To Byte Array):
Функция Waleed Eissa с поддержкой нижнего регистра:
источник
Методы расширения (отказ от ответственности: полностью непроверенный код, кстати ...):
и т. д. Используйте одно из трех решений Томалака (последнее из которых является методом расширения строки).
источник
От разработчиков Microsoft, хорошее, простое преобразование:
Хотя вышесказанное чисто и компактно, любители производительности будут кричать об этом, используя счетчики. Вы можете добиться максимальной производительности с улучшенной версией оригинального ответа Томалака :
Это самая быстрая из всех подпрограмм, которые я видел здесь до сих пор. Не просто поверьте мне на слово ... протестируйте производительность каждой подпрограммы и проверьте ее код CIL для себя.
источник
b.ToSting("X2")
.И для вставки в строку SQL (если вы не используете параметры команды):
источник
Source == null
илиSource.Length == 0
у нас есть проблемы, сэр!С точки зрения скорости это кажется лучше, чем что-либо здесь:
источник
Я не получил код, который ты предложил работать, Олипро.
hex[i] + hex[i+1]
видимо вернулint
.Однако я добился определенного успеха, взяв несколько подсказок из кода Waleeds и собрав их вместе. Это ужасно ужасно, но, кажется, работает и работает в 1/3 времени по сравнению с другими, согласно моим тестам (с использованием механизма тестирования патронов). В зависимости от размера ввода. Переключение вокруг?: S для выделения 0-9 первым, вероятно, приведет к несколько более быстрому результату, поскольку в нем больше цифр, чем букв.
источник
Эта версия ByteArrayToHexViaByteManipulation может быть быстрее.
Из моих отчетов:
...
И я думаю, что это оптимизация:
источник
Я приму участие в этом соревновании по битрейту, так как у меня есть ответ, который также использует битовую скрипку для декодирования шестнадцатеричных чисел. Обратите внимание, что использование символьных массивов может быть еще быстрее, так как вызов
StringBuilder
методов также займет время.Преобразовано из кода Java.
источник
Char[]
и использоватьChar
внутренне вместо целых ...Для производительности я бы пошел с раствором drphrozens. Небольшой оптимизацией для декодера может быть использование таблицы для любого символа, чтобы избавиться от «<< 4».
Очевидно, что два вызова метода являются дорогостоящими. Если какая-либо проверка выполняется на входных или выходных данных (может быть CRC, контрольная сумма или что-то еще),
if (b == 255)...
можно пропустить и, таким образом, также вызвать метод в целом.Использование
offset++
иoffset
вместоoffset
иoffset + 1
может дать некоторое теоретическое преимущество, но я подозреваю, что компилятор справляется с этим лучше, чем я.Это только с моей головы и не было проверено или сравнительно.
источник
Еще один вариант разнообразия:
источник
Не оптимизирован для скорости, но больше LINQy, чем большинство ответов (.NET 4.0):
источник
Два коллажа, которые складывают две клевы в одну.
Вероятно, довольно эффективная версия:
Декадентская версия linq-with-bit-hacking:
И наоборот:
источник
Другой способ - использовать
stackalloc
для уменьшения давления памяти GC:источник
Вот мой выстрел в это. Я создал пару классов расширения для расширения строки и байта. При тестировании больших файлов производительность сопоставима с Byte Manipulation 2.
Код ниже для ToHexString - оптимизированная реализация алгоритма поиска и сдвига. Он почти идентичен Behrooz, но оказывается, что он использует
foreach
итерацию, а счетчик работает быстрее, чем явное индексирование.for
.Он занимает второе место после Byte Manipulation 2 на моей машине и является очень читабельным кодом. Следующие результаты испытаний также представляют интерес:
ToHexStringCharArrayWithCharArrayLookup: 41 589,69 средних тиков (более 1000 прогонов), 1,5X ToHexStringCharArrayWithStringLookup: 50 764,06 средних тиков (более 1000 прогонов), 1,2X
Исходя из приведенных выше результатов, можно с уверенностью заключить, что:
Вот код:
Ниже приведены результаты тестирования, которые я получил, когда поместил свой код в проект тестирования @ patridge на моей машине. Я также добавил тест для преобразования байтового массива из шестнадцатеричного. Тестовые прогоны, которые выполняли мой код: ByteArrayToHexViaOptimizedLookupAndShift и HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte был взят из XXXX. HexToByteArrayViaSoapHexBinary - это ответ @ Mykroft.
источник
Еще одна быстрая функция ...
источник