Przyjmowana poniżej odpowiedź wydaje się przydzielać straszną liczbę ciągów w konwersji ciągu na bajty. Zastanawiam się, jak to wpływa na wydajność
Wim Coenen,
9
Myślę, że klasa SoapHexBinary robi dokładnie to, co chcesz.
Mykroft
Wydaje mi się, że zadawanie 2 pytań w 1 poście nie jest standardem.
SandRock
Odpowiedzi:
1353
Zarówno:
publicstaticstringByteArrayToString(byte[] ba){StringBuilder hex =newStringBuilder(ba.Length*2);foreach(byte b in ba)
hex.AppendFormat("{0:x2}", b);return hex.ToString();}
Istnieje jeszcze więcej wariantów robienia tego, na przykład tutaj .
Odwrotna konwersja wyglądałaby następująco:
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;}
Używanie Substringjest najlepszą opcją w połączeniu z Convert.ToByte. Zobacz tę odpowiedź, aby uzyskać więcej informacji. Jeśli potrzebujesz lepszej wydajności, musisz tego uniknąć, Convert.ToBytezanim spadniesz SubString.
Używasz SubString. Czy ta pętla nie przydziela strasznej liczby obiektów łańcuchowych?
Wim Coenen,
30
Szczerze mówiąc - dopóki dramatycznie nie obniży to wydajności, zwykle zignoruję to i ufam, że Runtime i GC się tym zajmą.
Tomalak
87
Ponieważ bajt to dwie wartości, każdy ciąg szesnastkowy, który poprawnie reprezentuje tablicę bajtów, musi mieć parzystą liczbę znaków. Nigdzie nie należy dodawać wartości 0 - dodanie tej wartości oznaczałoby założenie, że potencjalnie niebezpieczne dane są nieprawidłowe. Jeśli już, metoda StringToByteArray powinna zgłosić wyjątek formatu, jeśli ciąg szesnastkowy zawiera nieparzystą liczbę znaków.
David Boike,
7
@ 00jt Musisz założyć, że F == 0F. Albo jest to to samo co 0F, lub wejście zostało obcięte, a F jest właściwie początkiem czegoś, czego nie otrzymałeś. Przyjmowanie tych założeń zależy od twojego kontekstu, ale uważam, że funkcja ogólnego przeznaczenia powinna odrzucać nieparzyste znaki jako niepoprawne zamiast przyjmować takie założenie dla kodu wywołującego.
David Boike
11
@DavidBoike Pytanie nie miało nic wspólnego z „sposobem postępowania z potencjalnie obciętymi wartościami strumienia”. Mówi o łańcuchu znaków. String myValue = 10.ToString („X”); myValue to „A”, a nie „0A”. Teraz przeczytaj ten ciąg z powrotem do bajtów, oops go złamałeś.
00jt
488
Analiza wydajności
Uwaga: nowy lider od 20.08.2015.
Przeanalizowałem każdą z różnych metod konwersji przez pewne prymitywne Stopwatchtesty wydajności, przebieg z losowym zdaniem (n = 61, 1000 iteracji) i przebieg z tekstem Projektu Gutenburga (n = 1 238 957, 150 iteracji). Oto wyniki, z grubsza od najszybszego do najwolniejszego. Wszystkie pomiary są w tikach ( 10 000 tików = 1 ms ), a wszystkie uwagi względne są porównywane z [najwolniejszą] StringBuilderimplementacją. Informacje na temat użytego kodu znajdują się poniżej lub w repozytorium środowiska testowego, w którym teraz utrzymuję kod do uruchomienia tego.
Zrzeczenie się
OSTRZEŻENIE: Nie polegaj na tych statystykach na niczym konkretnym; są po prostu próbką danych przykładowych. Jeśli naprawdę potrzebujesz najwyższej wydajności, przetestuj te metody w środowisku reprezentatywnym dla twoich potrzeb produkcyjnych, z danymi reprezentującymi to, czego będziesz używać.
Tabele odnośników przejęły kontrolę nad przetwarzaniem bajtów. Zasadniczo istnieje pewna forma wstępnego obliczania tego, co dany skubek lub bajt będzie w formacie szesnastkowym. Następnie, przeglądając dane, po prostu przeglądasz kolejną część, aby zobaczyć, jaki to byłby szesnastkowy ciąg znaków. Ta wartość jest następnie dodawana do wynikowego ciągu w pewien sposób. Przez długi czas manipulowanie bajtami, potencjalnie trudniejsze do odczytania przez niektórych programistów, było najskuteczniejszym podejściem.
Najlepszym rozwiązaniem będzie znalezienie reprezentatywnych danych i wypróbowanie ich w środowisku produkcyjnym. Jeśli masz inne ograniczenia pamięci, możesz preferować metodę z mniejszą alokacją niż taką, która byłaby szybsza, ale zużywałaby więcej pamięci.
Kod testowy
Zachęcamy do zabawy przy użyciu kodu testowego, którego użyłem. Dołączona jest tutaj wersja, ale możesz sklonować repozytorium i dodać własne metody. Prześlij żądanie ściągnięcia, jeśli znajdziesz coś interesującego lub chcesz ulepszyć używaną platformę testową.
Dodaj nową metodę statyczną ( Func<byte[], string>) do /Tests/ConvertByteArrayToHexString/Test.cs.
Dodaj nazwę tej metody do TestCandidateswartości zwracanej w tej samej klasie.
Upewnij się, że korzystasz z żądanej wersji wejściowej, zdania lub tekstu, przełączając komentarze GenerateTestInputw tej samej klasie.
Naciśnij F5i poczekaj na wynik (zrzut HTML jest również generowany w folderze / 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();}
Aktualizacja (13.01.2010)
Dodano odpowiedź Waleeda do analizy. Dosyć szybko.
Aktualizacja (2011-10-05)
Dodano string.ConcatArray.ConvertAllwariant kompletności (wymaga .NET 4.0). Na równi z string.Joinwersją.
Aktualizacja (2012-02-05)
Repozytorium testów zawiera więcej wariantów, takich jak StringBuilder.Append(b.ToString("X2")). Żadne nie zakłóciło żadnych wyników. foreachjest szybszy niż {IEnumerable}.Aggregate, na przykład, ale BitConverterwciąż wygrywa.
Aktualizacja (2012-04-03)
Dodano SoapHexBinaryodpowiedź Mykroft do analizy, która zajęła trzecie miejsce.
Aktualizacja (15.01.2013)
Dodano odpowiedź manipulacji bajtami CodesInChaos, która zajęła pierwsze miejsce (z dużym marginesem na dużych blokach tekstu).
Aktualizacja (23.05.2013)
Dodano odpowiedź Nathana Moinvaziriego i wariant z bloga Briana Lamberta. Oba są dość szybkie, ale nie przejmują prowadzenia na testowej maszynie, z której korzystałem (AMD Phenom 9750).
Aktualizacja (2014-07-31)
Dodano nową odpowiedź bajtową @ CodesInChaos. Wydaje się, że przejął inicjatywę zarówno w testach zdań, jak i testach pełnotekstowych.
Aktualizacja (2015-08-20)
Dodano optymalizacje i wariant airbreatherunsafe do repozytorium tej odpowiedzi . Jeśli chcesz zagrać w niebezpieczną grę, możesz uzyskać ogromny wzrost wydajności w stosunku do któregokolwiek z poprzednich najlepszych zwycięzców zarówno w przypadku krótkich ciągów, jak i dużych tekstów.
Pomimo udostępnienia kodu do zrobienia tego, o co sam prosiłeś, zaktualizowałem kod testowy, aby zawierał odpowiedź Waleeda. Pomijając zrzędliwość, jest znacznie szybsza.
patridge
2
@CodesInChaos Gotowe. I całkiem sporo wygrał w moich testach. Nie udaję, że w pełni rozumiem jedną z najlepszych metod, ale można je łatwo ukryć przed bezpośrednią interakcją.
patridge
6
Ta odpowiedź nie ma na celu udzielenia odpowiedzi na pytanie, co jest „naturalne” lub powszechne. Celem jest przekazanie ludziom podstawowych wzorców wydajności, ponieważ gdy trzeba wykonać tę konwersję, często się zdarza. Jeśli ktoś potrzebuje prędkości pierwotnej, po prostu uruchamia testy porównawcze z odpowiednimi danymi testowymi w pożądanym środowisku komputerowym. Następnie umieść tę metodę w metodzie rozszerzenia, w której już nigdy nie spojrzysz na jej implementację (np bytes.ToHexStringAtLudicrousSpeed().).
patridge
2
Właśnie stworzyłem wysokowydajną implementację opartą na tabeli odnośników. Jego bezpieczny wariant jest o około 30% szybszy niż obecny lider mojego procesora. Niebezpieczne warianty są jeszcze szybsze. stackoverflow.com/a/24343727/445517
CodesInChaos
244
Istnieje klasa SoapHexBinary, która robi dokładnie to, co chcesz.
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 nie jest obsługiwany w .NET Core / .NET Standard ...
juFo
141
Podczas pisania kodu kryptograficznego często zdarza się, aby unikać gałęzi zależnych od danych i przeszukiwania tabel, aby upewnić się, że środowisko wykonawcze nie zależy od danych, ponieważ czas zależny od danych może prowadzić do ataków bocznych.
Jest również dość szybki.
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);}
Porzucić wszelką nadzieję wy, którzy wchodzą tutaj
Wyjaśnienie tego dziwnego bicia:
bytes[i] >> 4wyodrębnia wysoką część bajtu bytes[i] & 0xFwyodrębnia niską wartość części bajtu
b - 10
jest < 0dla wartości b < 10, które staną się cyfrą dziesiętną
jest >= 0dla wartości b > 10, które staną się literą od Ado F.
Użycie i >> 3132-bitowej liczby całkowitej ze znakiem powoduje wyodrębnienie znaku dzięki rozszerzeniu znaku. Będzie -1za i < 0i 0za i >= 0.
Łączenie 2) i 3) pokazuje, że (b-10)>>31będą to 0litery i -1cyfry.
Patrząc na sprawy do listów, ostatni do składnika staje 0, a bmieści się w zakresie od 10 do 15. Chcemy mapować go do A(65) do F(70), co oznacza dodanie 55 ( 'A'-10).
Patrząc na przypadek cyfr, chcemy dostosować ostatnie podsumowanie, aby mapowało się ono bw zakresie od 0 do 9 do zakresu 0(48) do 9(57). Oznacza to, że musi być -7 ( '0' - 55).
Teraz możemy po prostu pomnożyć przez 7. Ale ponieważ -1 jest reprezentowane przez wszystkie bity równe 1, możemy zamiast tego używać & -7od (0 & -7) == 0i (-1 & -7) == -7.
Kilka dalszych uwag:
Nie użyłem drugiej zmiennej pętli do indeksowania c, ponieważ pomiar pokazuje, że obliczenie jej na podstawie ijest tańsze.
Użycie dokładnie i < bytes.Lengthjako górnej granicy pętli pozwala JITterowi na wyeliminowanie sprawdzania granic bytes[i], więc wybrałem ten wariant.
Utworzenie bint pozwala na niepotrzebne konwersje zi do bajtu.
Jeszcze krótszy: String.Concat (Array.ConvertAll (bajty, x => x.ToString („X2”))
Nestor,
14
Jeszcze krótszy: String.Concat (bytes.Select (b => b.ToString („X2”)))) [.NET4]
Allon Guralnek
14
Odpowiada tylko na połowę pytania.
Sly Gryphon
1
Dlaczego drugi potrzebuje .Net 4? String.Concat znajduje się w .Net 2.0.
Polyfun
2
te pętle „w stylu lat 90-tych” są na ogół szybsze, ale w tak niewielkiej ilości, że w większości kontekstów nie mają znaczenia. Nadal jednak warto wspomnieć
Austin_Anderson
69
Inne podejście oparte na tabeli odnośników. Ten używa tylko jednej tabeli odnośników dla każdego bajtu, zamiast tabeli odnośników na skubanie.
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);}
I również badane warianty to za pomocą ushort, struct{char X1, X2}, struct{byte X1, X2}w tabeli przeglądowej.
W zależności od celu kompilacji (x86, X64) te albo miały mniej więcej tę samą wydajność lub były nieco wolniejsze niż ten wariant.
A dla jeszcze wyższej wydajności jego unsaferodzeństwo:
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);}
Lub jeśli uważasz, że dopuszczalne jest zapisanie bezpośrednio w ciągu:
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;}
Dlaczego tworzenie tabeli odnośników w niebezpiecznej wersji zamienia ciągi wstępnie obliczonego bajtu? Myślałem, że endianizm zmienił jedynie kolejność bytów, które powstały z wielu bajtów.
Raif Atef
@ RaifAtef Nie ma znaczenia kolejność skubków. Ale kolejność 16-bitowych słów w 32-bitowej liczbie całkowitej. Ale rozważam przepisanie go, aby ten sam kod mógł działać niezależnie od endianizmu.
CodesInChaos
Ponownie czytając kod, myślę, że to zrobiłeś, ponieważ kiedy rzucisz char * później na uint * i przypiszesz go (podczas generowania znaku hex), środowisko wykonawcze / CPU przerzuci bajty (ponieważ uint nie jest traktowany jako tak samo jak 2 oddzielne 16-bitowe znaki), więc odwracasz je w celu kompensacji. Czy mam rację ? Endianness jest mylący :-).
Raif Atef
4
To tylko odpowiedź na połowę pytania ... Co powiesz na ciąg znaków szesnastkowych na bajty?
Narvalex,
3
@CodesInChaos Zastanawiam się, czy Spanmożna go teraz używać zamiast unsafe??
Właśnie dzisiaj spotkałem ten sam problem i natknąłem się na ten kod:
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);}
Źródło: Post post byte [] Array to Hex String (patrz post PZahra). Zmodyfikowałem trochę kod, aby usunąć prefiks 0x.
Przeprowadziłem testy wydajności kodu i było ono prawie osiem razy szybsze niż użycie BitConverter.ToString () (najszybszy zgodnie z postem Patridge'a).
nie wspominając o tym, że zużywa najmniej pamięci. Nie utworzono żadnych łańcuchów pośrednich.
Chochos
8
Odpowiada tylko na połowę pytania.
Sly Gryphon
Jest to świetne, ponieważ działa w zasadzie na dowolnej wersji NET, w tym NETMF. Zwycięzca!
Jonesome przywraca Monikę
1
Przyjęta odpowiedź zawiera 2 doskonałe metody HexToByteArray, które reprezentują drugą połowę pytania. Rozwiązanie Waleeda odpowiada na bieżące pytanie, jak to zrobić bez tworzenia ogromnej liczby ciągów w tym procesie.
Brendten Eickstaedt
Czy nowy ciąg (c) kopiuje i ponownie przydziela lub czy jest wystarczająco inteligentny, aby wiedzieć, kiedy może po prostu owinąć char []?
Wyjaśnię, że ta edycja jest błędna, i wyjaśnię, dlaczego można ją cofnąć. Po drodze możesz dowiedzieć się czegoś o niektórych elementach wewnętrznych i zobaczyć jeszcze jeden przykład tego, czym tak naprawdę jest przedwczesna optymalizacja i jak może cię ugryźć.
tl; dr: Po prostu użyj Convert.ToBytei String.Substringjeśli się spieszysz („Kod oryginalny” poniżej), jest to najlepsza kombinacja, jeśli nie chcesz ponownie wdrożyć Convert.ToByte. Użyj czegoś bardziej zaawansowanego (zobacz inne odpowiedzi), z którego nie skorzystasz, Convert.ToBytejeśli potrzebujesz wydajności. Czy nie używać niczego innego niż String.Substringw połączeniu z Convert.ToByte, chyba że ktoś ma coś ciekawego do powiedzenia na ten temat w komentarzach tej odpowiedzi.
ostrzeżenie: Ta odpowiedź może stać się nieaktualna, jeśli w ramach Convert.ToByte(char[], Int32)zostanie zaimplementowane przeciążenie. Jest mało prawdopodobne, aby stało się to wkrótce.
Zasadniczo nie lubię mówić „nie optymalizuj przedwcześnie”, ponieważ nikt nie wie, kiedy jest „przedwczesny”. Jedyną rzeczą, którą należy wziąć pod uwagę przy podejmowaniu decyzji o optymalizacji, jest: „Czy mam czas i zasoby, aby właściwie zbadać podejścia optymalizacyjne?”. Jeśli tego nie zrobisz, to jest za wcześnie, poczekaj, aż twój projekt stanie się bardziej dojrzały lub do momentu, gdy będziesz potrzebować wydajności (jeśli jest taka potrzeba, to poświęcisz czas). W międzyczasie zrób najprostszą rzecz, która może działać.
Oryginalny kod:
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;}
Wersja 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;}
Wersja unika String.Substringi używa StringReaderzamiast niej. Podany powód to:
Patrząc na kod referencyjnyString.Substring , jest już wyraźnie „jednoprzebiegowy”; a dlaczego nie? Działa na poziomie bajtów, a nie na parach zastępczych.
Przydziela jednak nowy ciąg, ale i tak musisz go przydzielić Convert.ToByte. Ponadto rozwiązanie przedstawione w wersji przydziela kolejny obiekt na każdej iteracji (tablica dwuznakowa); możesz bezpiecznie umieścić ten przydział poza pętlą i ponownie użyć tablicy, aby tego uniknąć.
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;}
Każdy szesnastkowy numeralreprezentuje pojedynczy oktet przy użyciu dwóch cyfr (symboli).
Ale dlaczego dzwonisz StringReader.Readdwa razy? Po prostu wywołaj drugie przeciążenie i poproś, aby odczytał dwa znaki jednocześnie w tablicy dwóch znaków; i zmniejsz liczbę połączeń o dwa.
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;}
Pozostaje Ci czytnik łańcuchów, którego jedyną „wartością” jest indeks równoległy (wewnętrzny _pos), który mógłbyś zadeklarować ( jna przykład), redundantna zmienna długości (wewnętrzna _length) i redundantne odniesienie do danych wejściowych ciąg (wewnętrzny _s). Innymi słowy, jest bezużyteczny.
Jeśli zastanawiasz się, jak Read„czyta”, wystarczy spojrzeć na kod , wystarczy wywołać String.CopyTociąg wejściowy. Reszta to po prostu księgowość, aby zachować wartości, których nie potrzebujemy.
Więc usuń już czytnik ciągów i zadzwoń do CopyTosiebie; jest prostszy, jaśniejszy i bardziej wydajny.
Czy naprawdę potrzebujesz jindeksu, który zwiększa się w krokach po dwa równolegle i? Oczywiście nie, wystarczy pomnożyć iprzez dwa (które kompilator powinien być w stanie zoptymalizować do dodatku).
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;}
Jak teraz wygląda rozwiązanie? Dokładnie tak, jak było na początku, tylko zamiast String.Substringprzydzielić do przydzielenia ciągu i skopiowania do niego danych, używasz tablicy pośredniej, do której skopiujesz liczby szesnastkowe, a następnie przydzielisz ciąg i skopiujesz dane ponownie z tablicę i ciąg (po przekazaniu go w konstruktorze ciągu). Druga kopia może zostać zoptymalizowana, jeśli ciąg znajduje się już w puli wewnętrznej, ale String.Substringw takich przypadkach będzie można go również uniknąć.
W rzeczywistości, jeśli spojrzysz String.Substringponownie, zobaczysz, że wykorzystuje on pewną wewnętrzną wiedzę niskiego poziomu o tym, jak skonstruowane są łańcuchy, aby przydzielić ciąg szybciej niż można to normalnie zrobić, i wstawia ten sam kod używany CopyTobezpośrednio tam, aby uniknąć koszt połączenia.
String.Substring
Najgorszy przypadek: jeden szybki przydział, jeden szybki egzemplarz.
Najlepszy przypadek: bez przydziału, bez kopii.
Metoda ręczna
Najgorszy przypadek: dwa normalne przydziały, jeden zwykły egzemplarz, jeden szybki egzemplarz.
Najlepszy przypadek: jeden normalny przydział, jedna normalna kopia.
Wniosek? Jeśli chcesz skorzystaćConvert.ToByte(String, Int32) (ponieważ nie chcesz samodzielnie wdrożyć tej funkcji), nie ma sposobu na pokonanie String.Substring; wszystko, co robisz, to bieganie w kółko, ponowne wymyślanie koła (tylko z materiałami nieoptymalnymi).
Pamiętaj, że użycie Convert.ToBytei String.Substringjest doskonałym wyborem, jeśli nie potrzebujesz ekstremalnej wydajności. Pamiętaj: wybierz alternatywę tylko wtedy, gdy masz czas i zasoby, aby sprawdzić, jak działa ona poprawnie.
Gdyby tak było Convert.ToByte(char[], Int32), rzeczy byłyby oczywiście inne (byłoby możliwe zrobienie tego, co opisałem powyżej i całkowite uniknięcie String).
Podejrzewam, że ludzie, którzy zgłaszają lepszą wydajność poprzez „unikanie String.Substring”, również unikają Convert.ToByte(String, Int32), co naprawdę powinieneś zrobić, jeśli i tak potrzebujesz wydajności. Spójrz na niezliczone inne odpowiedzi, aby odkryć różne podejścia do tego celu.
Oświadczenie: Nie zdekompilowałem najnowszej wersji frameworka, aby sprawdzić, czy źródło referencyjne jest aktualne, zakładam, że tak jest.
Teraz wszystko brzmi dobrze i logicznie, miejmy nadzieję nawet oczywiste, jeśli udało ci się dotrzeć tak daleko. Ale czy to prawda?
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
Tak!
Podpory dla kuropatwy na ławce, łatwo jest zhakować. Zastosowano dane wejściowe: następujący skrót SHA-1 powtórzony 5000 razy, aby utworzyć łańcuch o długości 100 000 bajtów.
error: {"Nie można znaleźć żadnych rozpoznawalnych cyfr."}
Priya Jagtap
17
Uzupełnienie odpowiedzi przez @CodesInChaos (metoda odwrócona)
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;}
Wyjaśnienie:
& 0x0f ma również obsługiwać małe litery
hi = hi + 10 + ((hi >> 31) & 7); jest taki sam jak:
hi = ch-65 + 10 + (((ch-65) >> 31) & 7);
Dla „0” .. „9” jest to to samo, hi = ch - 65 + 10 + 7;co jest hi = ch - 48(wynika to z 0xffffffff & 7).
W przypadku „A” .. „F” jest hi = ch - 65 + 10;(z powodu 0x00000000 & 7).
W przypadku „a” .. „f” mamy duże liczby, więc musimy odjąć 32 od wersji domyślnej, tworząc pewne bity 0za pomocą & 0x0f.
65 to kod 'A'
48 to kod '0'
7 to liczba liter między '9'iw 'A'tabeli ASCII ( ...456789:;<=>?@ABCD...).
Ten problem można również rozwiązać za pomocą tabeli przeglądowej. Wymagałoby to niewielkiej ilości pamięci statycznej zarówno dla kodera, jak i dekodera. Ta metoda będzie jednak szybka:
Tabela enkoderów 512 bajtów lub 1024 bajtów (dwukrotność rozmiaru, jeśli potrzebne są zarówno duże, jak i małe litery)
Tabela dekodera 256 bajtów lub 64 KiB (wyszukiwanie pojedynczych znaków lub wyszukiwanie podwójnych znaków)
Moje rozwiązanie używa 1024 bajtów na tablicę kodowania i 256 bajtów na dekodowanie.
Rozszyfrowanie
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]));}
Kodowanie
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]];}
Podczas dekodowania może wystąpić wyjątek IOException i IndexOutOfRangeException (jeśli znak ma zbyt wysoką wartość> 256). Należy wdrożyć metody de / kodowania strumieni lub tablic, to tylko dowód koncepcji.
Zużycie pamięci 256 bajtów jest nieistotne, gdy uruchamiasz kod na CLR.
dolmen
9
To jest świetny post. Podoba mi się rozwiązanie Waleeda. Nie przeprowadziłem testu Patridge'a, ale wydaje się, że jest dość szybki. Potrzebowałem również procesu odwrotnego, przekształcając ciąg szesnastkowy w tablicę bajtów, więc napisałem go jako odwrócenie rozwiązania Waleeda. Nie jestem pewien, czy jest to szybsze niż oryginalne rozwiązanie Tomalaka. Ponownie nie przeprowadziłem również procesu odwrotnego poprzez test Patridge'a.
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;}
Ten kod zakłada, że ciąg szesnastkowy używa wielkich liter alfabetu, i wysadza się w powietrze, jeśli ciąg szesnastkowy używa małych liter alfabetu. Może być konieczne wykonanie konwersji „wielkich liter” na łańcuchu wejściowym, aby był bezpieczny.
Marc Novakowski
To sprytna obserwacja, Marc. Kod został napisany w celu odwrócenia rozwiązania Waleeda. Wywołanie ToUpper spowolniłoby algorytm, ale pozwoliłoby mu obsługiwać małe litery alfabetu.
Chris F
3
Convert.ToByte (topChar + bottomChar) można zapisać jako (bajt) (topChar + bottomChar)
Amir Rezaei
Aby poradzić sobie w obu przypadkach bez dużej kary za wyniki,hexString[i] &= ~0x20;
Ben Voigt
9
Po co to komplikować? Jest to proste w Visual Studio 2008:
powodem jest wydajność, gdy potrzebujesz rozwiązania o wysokiej wydajności. :)
Ricky
7
Nie stosuję tutaj wielu odpowiedzi, ale znalazłem dość optymalną (~ 4,5x lepszą niż zaakceptowana), prostą implementację parsera ciągu szesnastkowego. Po pierwsze, wynik moich testów (pierwsza partia to moja implementacja):
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
Linie base64 i „BitConverter'd” służą do testowania poprawności. Zauważ, że są równe.
Implementacja:
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;}
Próbowałem trochę rzeczy unsafei przesunąłem (wyraźnie redundantną) ifsekwencję znaków do skubania na inną metodę, ale była to najszybsza metoda.
(Przyznaję, że to odpowiada na połowę pytania. Czułem, że konwersja string-> bajt [] była niewystarczająco reprezentowana, podczas gdy kąt bajt [] -> wydaje się być dobrze uwzględniony. Zatem ta odpowiedź.)
Dla wyznawców Knuth: Zrobiłem to, ponieważ muszę analizować kilka tysięcy ciągów hex co kilka minut, więc ważne jest, aby było tak szybkie, jak to możliwe (jakby w wewnętrznej pętli). Rozwiązanie Tomalaka nie jest znacznie wolniejsze, jeśli nie występuje wiele takich analiz.
Ben Mosher
5
Bezpieczne wersje:
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;}}}
Niebezpieczne wersje Dla tych, którzy wolą wydajność i nie boją się niepewności. O 35% szybszy ToHex i 10% szybszy 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;}}}
BTW
W przypadku testu porównawczego inicjowanie alfabetu za każdym razem, gdy wywoływana funkcja konwersji jest niepoprawna, alfabet musi być const (dla ciągu) lub static readonly (dla char []). Następnie konwersja bajtu [] na ciąg alfabetyczny staje się tak szybka, jak wersje do manipulacji bajtami.
I oczywiście test musi zostać skompilowany w wersji (z optymalizacją) i wyłączonej opcji debugowania „Pomiń optymalizację JIT” (to samo dla „Włącz tylko mój kod”, jeśli kod musi być debuggowany).
Funkcja odwrotna dla kodu Waleed Eissa (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;}
Funkcja Waleed Eissa z obsługą małych liter:
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);}
Metody rozszerzenia (zrzeczenie się: kod całkowicie nieprzetestowany, BTW ...):
publicstaticclassByteExtensions{publicstaticstringToHexString(thisbyte[] ba){StringBuilder hex =newStringBuilder(ba.Length*2);foreach(byte b in ba){
hex.AppendFormat("{0:x2}", b);}return hex.ToString();}}
Prawdopodobnie powinieneś przetestować kod, zanim zaoferujesz go na takie pytanie.
jww
3
Od programistów Microsoftu, przyjemna, prosta konwersja:
publicstaticstringByteArrayToString(byte[] ba){// Concatenate the bytes into one long stringreturn ba.Aggregate(newStringBuilder(32),(sb, b)=> sb.Append(b.ToString("X2"))).ToString();}
Podczas gdy powyższe jest czyste i zwarte, ćpuny wydajności będą krzyczeć na ten temat za pomocą enumeratorów. Możesz uzyskać najwyższą wydajność dzięki ulepszonej wersji oryginalnej odpowiedzi Tomalaka :
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();}
Jest to najszybsza ze wszystkich procedur, jakie do tej pory widziałem tutaj. Nie wierz mi tylko na słowo ... przetestuj każdą procedurę i sprawdź sam jej kod CIL.
jeśli Source == nulllub Source.Length == 0mamy problem, proszę pana!
Andrei Krasutski
2
Pod względem prędkości wydaje się to lepsze niż cokolwiek tutaj:
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);}
Nie dostałem kodu, który zasugerowałeś, Olipro. hex[i] + hex[i+1]najwyraźniej zwrócił int.
Odniosłem jednak pewien sukces, czerpiąc pewne wskazówki z kodu Waleedsa i łącząc to ze sobą. To brzydkie jak diabli, ale wydaje się, że działa i działa w 1/3 czasu w porównaniu do innych, zgodnie z moimi testami (przy użyciu mechanizmu testowania łatek). W zależności od rozmiaru wejściowego. Przełączanie?: S, aby oddzielić 0-9 w pierwszej kolejności, prawdopodobnie dałoby nieco szybszy wynik, ponieważ jest więcej liczb niż liter.
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;}
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);}
Myślę, że ten jest optymalizacją:
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);}
Wezmę udział w tych zawodach, ponieważ mam odpowiedź, która również używa algorytmu do dekodowania liczb szesnastkowych. Pamiętaj, że używanie tablic znaków może być jeszcze szybsze, ponieważ StringBuildermetody wywoływania również zajmą trochę czasu.
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;}
Hmm, naprawdę powinienem to zoptymalizować Char[]i używać Charwewnętrznie zamiast ints ...
Maarten Bodewes,
W przypadku języka C # inicjowanie zmiennych tam, gdzie są używane, zamiast poza pętlą, jest prawdopodobnie preferowane, aby umożliwić kompilatorowi optymalizację. Tak czy inaczej uzyskuję równoważną wydajność.
Peteter
2
Dla wydajności wybrałbym rozwiązanie drphrozens. Niewielką optymalizacją dekodera może być użycie tabeli dla jednego z znaków, aby pozbyć się „<< 4”.
Oczywiście te dwa wywołania metod są kosztowne. Jeśli sprawdzane są dane wejściowe lub wyjściowe (może to być CRC, suma kontrolna lub cokolwiek innego)if (b == 255)... można je pominąć, a tym samym również wywołania metod.
Używanie offset++i offsetzamiast offseti offset + 1może dawać jakieś teoretyczne korzyści, ale podejrzewam, że kompilator radzi sobie z tym lepiej niż ja.
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]));}
Jest to tuż przy mojej głowie i nie zostało przetestowane ani przetestowane.
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;}
Dwa mashupy, które składają dwie operacje skubania w jedną.
Prawdopodobnie całkiem wydajna wersja:
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 );}
Dekadencka wersja 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))));}
I odwrotnie:
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”) zwraca 0x02, co jest złe
CoperNick
1
Innym sposobem jest stackalloczmniejszenie ciśnienia w pamięci 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);}
Oto mój strzał. Utworzyłem parę klas rozszerzeń, aby przedłużyć ciąg i bajt. W teście dużych plików wydajność jest porównywalna z Byte Manipulation 2.
Poniższy kod ToHexString jest zoptymalizowaną implementacją algorytmu wyszukiwania i przesunięcia. Jest prawie identyczny z Behroozem, ale okazuje się, foreachże iteruje, a licznik jest szybszy niż jawne indeksowaniefor .
Zajmuje 2 miejsce za Byte Manipulation 2 na moim komputerze i jest bardzo czytelnym kodem. Interesujące są również następujące wyniki testu:
Na podstawie powyższych wyników można bezpiecznie stwierdzić, że:
Kary za indeksowanie do łańcucha w celu wykonania wyszukiwania w porównaniu z tablicą znaków są znaczące w teście dużego pliku.
Kary za użycie StringBuilder o znanej pojemności w porównaniu do tablicy znaków o znanej wielkości do utworzenia łańcucha są jeszcze bardziej znaczące.
Oto kod:
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;}}}
Poniżej znajdują się wyniki testów, które otrzymałem po umieszczeniu mojego kodu w projekcie testowym @ patridge na moim komputerze. Dodałem również test do konwersji na tablicę bajtów z szesnastkowej. Testy, które wykonały mój kod, to ByteArrayToHexViaOptimizedLookupAndShift i HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte został pobrany z XXXX. HexToByteArrayViaSoapHexBinary jest tym z odpowiedzi @ Mykroft.
Odpowiedzi:
Zarówno:
lub:
Istnieje jeszcze więcej wariantów robienia tego, na przykład tutaj .
Odwrotna konwersja wyglądałaby następująco:
Używanie
Substring
jest najlepszą opcją w połączeniu zConvert.ToByte
. Zobacz tę odpowiedź, aby uzyskać więcej informacji. Jeśli potrzebujesz lepszej wydajności, musisz tego uniknąć,Convert.ToByte
zanim spadnieszSubString
.źródło
Analiza wydajności
Uwaga: nowy lider od 20.08.2015.
Przeanalizowałem każdą z różnych metod konwersji przez pewne prymitywne
Stopwatch
testy wydajności, przebieg z losowym zdaniem (n = 61, 1000 iteracji) i przebieg z tekstem Projektu Gutenburga (n = 1 238 957, 150 iteracji). Oto wyniki, z grubsza od najszybszego do najwolniejszego. Wszystkie pomiary są w tikach ( 10 000 tików = 1 ms ), a wszystkie uwagi względne są porównywane z [najwolniejszą]StringBuilder
implementacją. Informacje na temat użytego kodu znajdują się poniżej lub w repozytorium środowiska testowego, w którym teraz utrzymuję kod do uruchomienia tego.Zrzeczenie się
OSTRZEŻENIE: Nie polegaj na tych statystykach na niczym konkretnym; są po prostu próbką danych przykładowych. Jeśli naprawdę potrzebujesz najwyższej wydajności, przetestuj te metody w środowisku reprezentatywnym dla twoich potrzeb produkcyjnych, z danymi reprezentującymi to, czego będziesz używać.
Wyniki
unsafe
(przez CodesInChaos) (dodane do testowego repozytorium przez airbreather )BitConverter
(przez Tomalak){SoapHexBinary}.ToString
(przez Mykroft){byte}.ToString("X2")
(usingforeach
) (pochodzi z odpowiedzi Willa Deana){byte}.ToString("X2")
(używanie{IEnumerable}.Aggregate
, wymaga System.Linq) (przez Mark)Array.ConvertAll
(za pomocąstring.Join
) (przez Will Dean)Array.ConvertAll
(używaniestring.Concat
, wymaga .NET 4.0) (przez Will Dean){StringBuilder}.AppendFormat
(za pomocąforeach
) (przez Tomalak){StringBuilder}.AppendFormat
(używanie{IEnumerable}.Aggregate
, wymaga System.Linq) (pochodzi z odpowiedzi Tomalaka)Tabele odnośników przejęły kontrolę nad przetwarzaniem bajtów. Zasadniczo istnieje pewna forma wstępnego obliczania tego, co dany skubek lub bajt będzie w formacie szesnastkowym. Następnie, przeglądając dane, po prostu przeglądasz kolejną część, aby zobaczyć, jaki to byłby szesnastkowy ciąg znaków. Ta wartość jest następnie dodawana do wynikowego ciągu w pewien sposób. Przez długi czas manipulowanie bajtami, potencjalnie trudniejsze do odczytania przez niektórych programistów, było najskuteczniejszym podejściem.
Najlepszym rozwiązaniem będzie znalezienie reprezentatywnych danych i wypróbowanie ich w środowisku produkcyjnym. Jeśli masz inne ograniczenia pamięci, możesz preferować metodę z mniejszą alokacją niż taką, która byłaby szybsza, ale zużywałaby więcej pamięci.
Kod testowy
Zachęcamy do zabawy przy użyciu kodu testowego, którego użyłem. Dołączona jest tutaj wersja, ale możesz sklonować repozytorium i dodać własne metody. Prześlij żądanie ściągnięcia, jeśli znajdziesz coś interesującego lub chcesz ulepszyć używaną platformę testową.
Func<byte[], string>
) do /Tests/ConvertByteArrayToHexString/Test.cs.TestCandidates
wartości zwracanej w tej samej klasie.GenerateTestInput
w tej samej klasie.Aktualizacja (13.01.2010)
Dodano odpowiedź Waleeda do analizy. Dosyć szybko.
Aktualizacja (2011-10-05)
Dodano
string.Concat
Array.ConvertAll
wariant kompletności (wymaga .NET 4.0). Na równi zstring.Join
wersją.Aktualizacja (2012-02-05)
Repozytorium testów zawiera więcej wariantów, takich jak
StringBuilder.Append(b.ToString("X2"))
. Żadne nie zakłóciło żadnych wyników.foreach
jest szybszy niż{IEnumerable}.Aggregate
, na przykład, aleBitConverter
wciąż wygrywa.Aktualizacja (2012-04-03)
Dodano
SoapHexBinary
odpowiedź Mykroft do analizy, która zajęła trzecie miejsce.Aktualizacja (15.01.2013)
Dodano odpowiedź manipulacji bajtami CodesInChaos, która zajęła pierwsze miejsce (z dużym marginesem na dużych blokach tekstu).
Aktualizacja (23.05.2013)
Dodano odpowiedź Nathana Moinvaziriego i wariant z bloga Briana Lamberta. Oba są dość szybkie, ale nie przejmują prowadzenia na testowej maszynie, z której korzystałem (AMD Phenom 9750).
Aktualizacja (2014-07-31)
Dodano nową odpowiedź bajtową @ CodesInChaos. Wydaje się, że przejął inicjatywę zarówno w testach zdań, jak i testach pełnotekstowych.
Aktualizacja (2015-08-20)
Dodano optymalizacje i wariant airbreather
unsafe
do repozytorium tej odpowiedzi . Jeśli chcesz zagrać w niebezpieczną grę, możesz uzyskać ogromny wzrost wydajności w stosunku do któregokolwiek z poprzednich najlepszych zwycięzców zarówno w przypadku krótkich ciągów, jak i dużych tekstów.źródło
bytes.ToHexStringAtLudicrousSpeed()
.).Istnieje klasa SoapHexBinary, która robi dokładnie to, co chcesz.
źródło
Podczas pisania kodu kryptograficznego często zdarza się, aby unikać gałęzi zależnych od danych i przeszukiwania tabel, aby upewnić się, że środowisko wykonawcze nie zależy od danych, ponieważ czas zależny od danych może prowadzić do ataków bocznych.
Jest również dość szybki.
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn
Wyjaśnienie tego dziwnego bicia:
bytes[i] >> 4
wyodrębnia wysoką część bajtubytes[i] & 0xF
wyodrębnia niską wartość części bajtub - 10
jest
< 0
dla wartościb < 10
, które staną się cyfrą dziesiętnąjest
>= 0
dla wartościb > 10
, które staną się literą odA
doF
.i >> 31
32-bitowej liczby całkowitej ze znakiem powoduje wyodrębnienie znaku dzięki rozszerzeniu znaku. Będzie-1
zai < 0
i0
zai >= 0
.(b-10)>>31
będą to0
litery i-1
cyfry.0
, ab
mieści się w zakresie od 10 do 15. Chcemy mapować go doA
(65) doF
(70), co oznacza dodanie 55 ('A'-10
).b
w zakresie od 0 do 9 do zakresu0
(48) do9
(57). Oznacza to, że musi być -7 ('0' - 55
).Teraz możemy po prostu pomnożyć przez 7. Ale ponieważ -1 jest reprezentowane przez wszystkie bity równe 1, możemy zamiast tego używać
& -7
od(0 & -7) == 0
i(-1 & -7) == -7
.Kilka dalszych uwag:
c
, ponieważ pomiar pokazuje, że obliczenie jej na podstawiei
jest tańsze.i < bytes.Length
jako górnej granicy pętli pozwala JITterowi na wyeliminowanie sprawdzania granicbytes[i]
, więc wybrałem ten wariant.b
int pozwala na niepotrzebne konwersje zi do bajtu.źródło
hex string
dobyte[] array
?87 + b + (((b-10)>>31)&-39)
byte[] array
”, co dosłownie oznacza tablicę tablic bajtowych lubbyte[][]
. Po prostu się naśmiewałam.Jeśli chcesz większej elastyczności
BitConverter
, ale nie chcesz tych niezgrabnych wyraźnych pętli w stylu lat 90., możesz:Lub, jeśli używasz .NET 4.0:
(To ostatnie z komentarza do oryginalnego postu.)
źródło
Inne podejście oparte na tabeli odnośników. Ten używa tylko jednej tabeli odnośników dla każdego bajtu, zamiast tabeli odnośników na skubanie.
I również badane warianty to za pomocą
ushort
,struct{char X1, X2}
,struct{byte X1, X2}
w tabeli przeglądowej.W zależności od celu kompilacji (x86, X64) te albo miały mniej więcej tę samą wydajność lub były nieco wolniejsze niż ten wariant.
A dla jeszcze wyższej wydajności jego
unsafe
rodzeństwo:Lub jeśli uważasz, że dopuszczalne jest zapisanie bezpośrednio w ciągu:
źródło
Span
można go teraz używać zamiastunsafe
??Możesz użyć metody BitConverter.ToString:
Wynik:
Więcej informacji: BitConverter.ToString Method (Byte [])
źródło
Właśnie dzisiaj spotkałem ten sam problem i natknąłem się na ten kod:
Źródło: Post post byte [] Array to Hex String (patrz post PZahra). Zmodyfikowałem trochę kod, aby usunąć prefiks 0x.
Przeprowadziłem testy wydajności kodu i było ono prawie osiem razy szybsze niż użycie BitConverter.ToString () (najszybszy zgodnie z postem Patridge'a).
źródło
Jest to odpowiedź na zmiany 4 z bardzo popularnej odpowiedź Tomalak za (i kolejnych edycji).
Wyjaśnię, że ta edycja jest błędna, i wyjaśnię, dlaczego można ją cofnąć. Po drodze możesz dowiedzieć się czegoś o niektórych elementach wewnętrznych i zobaczyć jeszcze jeden przykład tego, czym tak naprawdę jest przedwczesna optymalizacja i jak może cię ugryźć.
tl; dr: Po prostu użyj
Convert.ToByte
iString.Substring
jeśli się spieszysz („Kod oryginalny” poniżej), jest to najlepsza kombinacja, jeśli nie chcesz ponownie wdrożyćConvert.ToByte
. Użyj czegoś bardziej zaawansowanego (zobacz inne odpowiedzi), z którego nie skorzystasz,Convert.ToByte
jeśli potrzebujesz wydajności. Czy nie używać niczego innego niżString.Substring
w połączeniu zConvert.ToByte
, chyba że ktoś ma coś ciekawego do powiedzenia na ten temat w komentarzach tej odpowiedzi.ostrzeżenie: Ta odpowiedź może stać się nieaktualna, jeśli w ramach
Convert.ToByte(char[], Int32)
zostanie zaimplementowane przeciążenie. Jest mało prawdopodobne, aby stało się to wkrótce.Zasadniczo nie lubię mówić „nie optymalizuj przedwcześnie”, ponieważ nikt nie wie, kiedy jest „przedwczesny”. Jedyną rzeczą, którą należy wziąć pod uwagę przy podejmowaniu decyzji o optymalizacji, jest: „Czy mam czas i zasoby, aby właściwie zbadać podejścia optymalizacyjne?”. Jeśli tego nie zrobisz, to jest za wcześnie, poczekaj, aż twój projekt stanie się bardziej dojrzały lub do momentu, gdy będziesz potrzebować wydajności (jeśli jest taka potrzeba, to poświęcisz czas). W międzyczasie zrób najprostszą rzecz, która może działać.
Oryginalny kod:
Wersja 4:
Wersja unika
String.Substring
i używaStringReader
zamiast niej. Podany powód to:Patrząc na kod referencyjny
String.Substring
, jest już wyraźnie „jednoprzebiegowy”; a dlaczego nie? Działa na poziomie bajtów, a nie na parach zastępczych.Przydziela jednak nowy ciąg, ale i tak musisz go przydzielić
Convert.ToByte
. Ponadto rozwiązanie przedstawione w wersji przydziela kolejny obiekt na każdej iteracji (tablica dwuznakowa); możesz bezpiecznie umieścić ten przydział poza pętlą i ponownie użyć tablicy, aby tego uniknąć.Każdy szesnastkowy
numeral
reprezentuje pojedynczy oktet przy użyciu dwóch cyfr (symboli).Ale dlaczego dzwonisz
StringReader.Read
dwa razy? Po prostu wywołaj drugie przeciążenie i poproś, aby odczytał dwa znaki jednocześnie w tablicy dwóch znaków; i zmniejsz liczbę połączeń o dwa.Pozostaje Ci czytnik łańcuchów, którego jedyną „wartością” jest indeks równoległy (wewnętrzny
_pos
), który mógłbyś zadeklarować (j
na przykład), redundantna zmienna długości (wewnętrzna_length
) i redundantne odniesienie do danych wejściowych ciąg (wewnętrzny_s
). Innymi słowy, jest bezużyteczny.Jeśli zastanawiasz się, jak
Read
„czyta”, wystarczy spojrzeć na kod , wystarczy wywołaćString.CopyTo
ciąg wejściowy. Reszta to po prostu księgowość, aby zachować wartości, których nie potrzebujemy.Więc usuń już czytnik ciągów i zadzwoń do
CopyTo
siebie; jest prostszy, jaśniejszy i bardziej wydajny.Czy naprawdę potrzebujesz
j
indeksu, który zwiększa się w krokach po dwa równoleglei
? Oczywiście nie, wystarczy pomnożyći
przez dwa (które kompilator powinien być w stanie zoptymalizować do dodatku).Jak teraz wygląda rozwiązanie? Dokładnie tak, jak było na początku, tylko zamiast
String.Substring
przydzielić do przydzielenia ciągu i skopiowania do niego danych, używasz tablicy pośredniej, do której skopiujesz liczby szesnastkowe, a następnie przydzielisz ciąg i skopiujesz dane ponownie z tablicę i ciąg (po przekazaniu go w konstruktorze ciągu). Druga kopia może zostać zoptymalizowana, jeśli ciąg znajduje się już w puli wewnętrznej, aleString.Substring
w takich przypadkach będzie można go również uniknąć.W rzeczywistości, jeśli spojrzysz
String.Substring
ponownie, zobaczysz, że wykorzystuje on pewną wewnętrzną wiedzę niskiego poziomu o tym, jak skonstruowane są łańcuchy, aby przydzielić ciąg szybciej niż można to normalnie zrobić, i wstawia ten sam kod używanyCopyTo
bezpośrednio tam, aby uniknąć koszt połączenia.String.Substring
Metoda ręczna
Wniosek? Jeśli chcesz skorzystać
Convert.ToByte(String, Int32)
(ponieważ nie chcesz samodzielnie wdrożyć tej funkcji), nie ma sposobu na pokonanieString.Substring
; wszystko, co robisz, to bieganie w kółko, ponowne wymyślanie koła (tylko z materiałami nieoptymalnymi).Pamiętaj, że użycie
Convert.ToByte
iString.Substring
jest doskonałym wyborem, jeśli nie potrzebujesz ekstremalnej wydajności. Pamiętaj: wybierz alternatywę tylko wtedy, gdy masz czas i zasoby, aby sprawdzić, jak działa ona poprawnie.Gdyby tak było
Convert.ToByte(char[], Int32)
, rzeczy byłyby oczywiście inne (byłoby możliwe zrobienie tego, co opisałem powyżej i całkowite uniknięcieString
).Podejrzewam, że ludzie, którzy zgłaszają lepszą wydajność poprzez „unikanie
String.Substring
”, również unikająConvert.ToByte(String, Int32)
, co naprawdę powinieneś zrobić, jeśli i tak potrzebujesz wydajności. Spójrz na niezliczone inne odpowiedzi, aby odkryć różne podejścia do tego celu.Oświadczenie: Nie zdekompilowałem najnowszej wersji frameworka, aby sprawdzić, czy źródło referencyjne jest aktualne, zakładam, że tak jest.
Teraz wszystko brzmi dobrze i logicznie, miejmy nadzieję nawet oczywiste, jeśli udało ci się dotrzeć tak daleko. Ale czy to prawda?
Tak!
Podpory dla kuropatwy na ławce, łatwo jest zhakować. Zastosowano dane wejściowe: następujący skrót SHA-1 powtórzony 5000 razy, aby utworzyć łańcuch o długości 100 000 bajtów.
Baw się dobrze! (Ale optymalizuj z umiarem).
źródło
Uzupełnienie odpowiedzi przez @CodesInChaos (metoda odwrócona)
Wyjaśnienie:
& 0x0f
ma również obsługiwać małe literyhi = hi + 10 + ((hi >> 31) & 7);
jest taki sam jak:hi = ch-65 + 10 + (((ch-65) >> 31) & 7);
Dla „0” .. „9” jest to to samo,
hi = ch - 65 + 10 + 7;
co jesthi = ch - 48
(wynika to z0xffffffff & 7
).W przypadku „A” .. „F” jest
hi = ch - 65 + 10;
(z powodu0x00000000 & 7
).W przypadku „a” .. „f” mamy duże liczby, więc musimy odjąć 32 od wersji domyślnej, tworząc pewne bity
0
za pomocą& 0x0f
.65 to kod
'A'
48 to kod
'0'
7 to liczba liter między
'9'
iw'A'
tabeli ASCII (...456789:;<=>?@ABCD...
).źródło
Ten problem można również rozwiązać za pomocą tabeli przeglądowej. Wymagałoby to niewielkiej ilości pamięci statycznej zarówno dla kodera, jak i dekodera. Ta metoda będzie jednak szybka:
Moje rozwiązanie używa 1024 bajtów na tablicę kodowania i 256 bajtów na dekodowanie.
Rozszyfrowanie
Kodowanie
Porównanie
* to rozwiązanie
Uwaga
Podczas dekodowania może wystąpić wyjątek IOException i IndexOutOfRangeException (jeśli znak ma zbyt wysoką wartość> 256). Należy wdrożyć metody de / kodowania strumieni lub tablic, to tylko dowód koncepcji.
źródło
To jest świetny post. Podoba mi się rozwiązanie Waleeda. Nie przeprowadziłem testu Patridge'a, ale wydaje się, że jest dość szybki. Potrzebowałem również procesu odwrotnego, przekształcając ciąg szesnastkowy w tablicę bajtów, więc napisałem go jako odwrócenie rozwiązania Waleeda. Nie jestem pewien, czy jest to szybsze niż oryginalne rozwiązanie Tomalaka. Ponownie nie przeprowadziłem również procesu odwrotnego poprzez test Patridge'a.
źródło
hexString[i] &= ~0x20;
Po co to komplikować? Jest to proste w Visual Studio 2008:
DO#:
VB:
źródło
Nie stosuję tutaj wielu odpowiedzi, ale znalazłem dość optymalną (~ 4,5x lepszą niż zaakceptowana), prostą implementację parsera ciągu szesnastkowego. Po pierwsze, wynik moich testów (pierwsza partia to moja implementacja):
Linie base64 i „BitConverter'd” służą do testowania poprawności. Zauważ, że są równe.
Implementacja:
Próbowałem trochę rzeczy
unsafe
i przesunąłem (wyraźnie redundantną)if
sekwencję znaków do skubania na inną metodę, ale była to najszybsza metoda.(Przyznaję, że to odpowiada na połowę pytania. Czułem, że konwersja string-> bajt [] była niewystarczająco reprezentowana, podczas gdy kąt bajt [] -> wydaje się być dobrze uwzględniony. Zatem ta odpowiedź.)
źródło
Bezpieczne wersje:
Niebezpieczne wersje Dla tych, którzy wolą wydajność i nie boją się niepewności. O 35% szybszy ToHex i 10% szybszy FromHex.
BTW W przypadku testu porównawczego inicjowanie alfabetu za każdym razem, gdy wywoływana funkcja konwersji jest niepoprawna, alfabet musi być const (dla ciągu) lub static readonly (dla char []). Następnie konwersja bajtu [] na ciąg alfabetyczny staje się tak szybka, jak wersje do manipulacji bajtami.
I oczywiście test musi zostać skompilowany w wersji (z optymalizacją) i wyłączonej opcji debugowania „Pomiń optymalizację JIT” (to samo dla „Włącz tylko mój kod”, jeśli kod musi być debuggowany).
źródło
Funkcja odwrotna dla kodu Waleed Eissa (Hex String To Byte Array):
Funkcja Waleed Eissa z obsługą małych liter:
źródło
Metody rozszerzenia (zrzeczenie się: kod całkowicie nieprzetestowany, BTW ...):
itd. Użyj jednego z trzech rozwiązań Tomalaka (ostatnie z nich to metoda rozszerzenia ciągu).
źródło
Od programistów Microsoftu, przyjemna, prosta konwersja:
Podczas gdy powyższe jest czyste i zwarte, ćpuny wydajności będą krzyczeć na ten temat za pomocą enumeratorów. Możesz uzyskać najwyższą wydajność dzięki ulepszonej wersji oryginalnej odpowiedzi Tomalaka :
Jest to najszybsza ze wszystkich procedur, jakie do tej pory widziałem tutaj. Nie wierz mi tylko na słowo ... przetestuj każdą procedurę i sprawdź sam jej kod CIL.
źródło
b.ToSting("X2")
.I do wstawiania do ciągu SQL (jeśli nie używasz parametrów polecenia):
źródło
Source == null
lubSource.Length == 0
mamy problem, proszę pana!Pod względem prędkości wydaje się to lepsze niż cokolwiek tutaj:
źródło
Nie dostałem kodu, który zasugerowałeś, Olipro.
hex[i] + hex[i+1]
najwyraźniej zwróciłint
.Odniosłem jednak pewien sukces, czerpiąc pewne wskazówki z kodu Waleedsa i łącząc to ze sobą. To brzydkie jak diabli, ale wydaje się, że działa i działa w 1/3 czasu w porównaniu do innych, zgodnie z moimi testami (przy użyciu mechanizmu testowania łatek). W zależności od rozmiaru wejściowego. Przełączanie?: S, aby oddzielić 0-9 w pierwszej kolejności, prawdopodobnie dałoby nieco szybszy wynik, ponieważ jest więcej liczb niż liter.
źródło
Ta wersja ByteArrayToHexViaByteManipulation może być szybsza.
Z moich raportów:
...
Myślę, że ten jest optymalizacją:
źródło
Wezmę udział w tych zawodach, ponieważ mam odpowiedź, która również używa algorytmu do dekodowania liczb szesnastkowych. Pamiętaj, że używanie tablic znaków może być jeszcze szybsze, ponieważ
StringBuilder
metody wywoływania również zajmą trochę czasu.Konwertowane z kodu Java.
źródło
Char[]
i używaćChar
wewnętrznie zamiast ints ...Dla wydajności wybrałbym rozwiązanie drphrozens. Niewielką optymalizacją dekodera może być użycie tabeli dla jednego z znaków, aby pozbyć się „<< 4”.
Oczywiście te dwa wywołania metod są kosztowne. Jeśli sprawdzane są dane wejściowe lub wyjściowe (może to być CRC, suma kontrolna lub cokolwiek innego)
if (b == 255)...
można je pominąć, a tym samym również wywołania metod.Używanie
offset++
ioffset
zamiastoffset
ioffset + 1
może dawać jakieś teoretyczne korzyści, ale podejrzewam, że kompilator radzi sobie z tym lepiej niż ja.Jest to tuż przy mojej głowie i nie zostało przetestowane ani przetestowane.
źródło
Kolejna odmiana różnorodności:
źródło
Niezoptymalizowany pod kątem szybkości, ale bardziej LINQy niż większość odpowiedzi (.NET 4.0):
źródło
Dwa mashupy, które składają dwie operacje skubania w jedną.
Prawdopodobnie całkiem wydajna wersja:
Dekadencka wersja linq-with-bit-hacking:
I odwrotnie:
źródło
Innym sposobem jest
stackalloc
zmniejszenie ciśnienia w pamięci GC:źródło
Oto mój strzał. Utworzyłem parę klas rozszerzeń, aby przedłużyć ciąg i bajt. W teście dużych plików wydajność jest porównywalna z Byte Manipulation 2.
Poniższy kod ToHexString jest zoptymalizowaną implementacją algorytmu wyszukiwania i przesunięcia. Jest prawie identyczny z Behroozem, ale okazuje się,
foreach
że iteruje, a licznik jest szybszy niż jawne indeksowaniefor
.Zajmuje 2 miejsce za Byte Manipulation 2 na moim komputerze i jest bardzo czytelnym kodem. Interesujące są również następujące wyniki testu:
ToHexStringCharArrayWithCharArrayLookup: 41 589,69 średnich tyknięć (ponad 1000 przebiegów), 1,5X ToHexStringCharArrayWithStringLookup: 50 764,06 średnich tyknięć (ponad 1000 przebiegów), 1,2X ToHexStringStringBuilderWithCharArrayLookup średnio: 10,88
Na podstawie powyższych wyników można bezpiecznie stwierdzić, że:
Oto kod:
Poniżej znajdują się wyniki testów, które otrzymałem po umieszczeniu mojego kodu w projekcie testowym @ patridge na moim komputerze. Dodałem również test do konwersji na tablicę bajtów z szesnastkowej. Testy, które wykonały mój kod, to ByteArrayToHexViaOptimizedLookupAndShift i HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte został pobrany z XXXX. HexToByteArrayViaSoapHexBinary jest tym z odpowiedzi @ Mykroft.
źródło
Kolejna szybka funkcja ...
źródło