Jak przekonwertować tablicę bajtów na ciąg szesnastkowy i odwrotnie?
Jak przekonwertować tablicę bajtów na ciąg szesnastkowy i odwrotnie?
Odpowiedzi:
Zarówno:
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
lub:
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
Istnieje jeszcze więcej wariantów robienia tego, na przykład tutaj .
Odwrotna konwersja wyglądałaby następująco:
public static byte[] StringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
Używanie Substring
jest 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.ToByte
zanim spadniesz SubString
.
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.
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ć.
unsafe
(przez CodesInChaos) (dodane do testowego repozytorium przez airbreather )
BitConverter
(przez Tomalak)
{SoapHexBinary}.ToString
(przez Mykroft)
{byte}.ToString("X2")
(using foreach
) (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żywanie string.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.
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.static string ByteArrayToHexStringViaStringJoinArrayConvertAll(byte[] bytes) {
return string.Join(string.Empty, Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaStringConcatArrayConvertAll(byte[] bytes) {
return string.Concat(Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaBitConverter(byte[] bytes) {
string hex = BitConverter.ToString(bytes);
return hex.Replace("-", "");
}
static string ByteArrayToHexStringViaStringBuilderAggregateByteToString(byte[] bytes) {
return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.Append(b.ToString("X2"))).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachByteToString(byte[] bytes) {
StringBuilder hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
hex.Append(b.ToString("X2"));
return hex.ToString();
}
static string ByteArrayToHexStringViaStringBuilderAggregateAppendFormat(byte[] bytes) {
return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.AppendFormat("{0:X2}", b)).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachAppendFormat(byte[] bytes) {
StringBuilder hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
hex.AppendFormat("{0:X2}", b);
return hex.ToString();
}
static string ByteArrayToHexViaByteManipulation(byte[] bytes) {
char[] c = new char[bytes.Length * 2];
byte b;
for (int i = 0; i < bytes.Length; i++) {
b = ((byte)(bytes[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(bytes[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
static string ByteArrayToHexViaByteManipulation2(byte[] bytes) {
char[] c = new char[bytes.Length * 2];
int b;
for (int i = 0; i < bytes.Length; i++) {
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
}
return new string(c);
}
static string ByteArrayToHexViaSoapHexBinary(byte[] bytes) {
SoapHexBinary soapHexBinary = new SoapHexBinary(bytes);
return soapHexBinary.ToString();
}
static string ByteArrayToHexViaLookupAndShift(byte[] bytes) {
StringBuilder result = new StringBuilder(bytes.Length * 2);
string hexAlphabet = "0123456789ABCDEF";
foreach (byte b in bytes) {
result.Append(hexAlphabet[(int)(b >> 4)]);
result.Append(hexAlphabet[(int)(b & 0xF)]);
}
return result.ToString();
}
static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_Lookup32, GCHandleType.Pinned).AddrOfPinnedObject();
static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes) {
var lookupP = _lookup32UnsafeP;
var result = new string((char)0, bytes.Length * 2);
fixed (byte* bytesP = bytes)
fixed (char* resultP = result) {
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++) {
resultP2[i] = lookupP[bytesP[i]];
}
}
return result;
}
static uint[] _Lookup32 = Enumerable.Range(0, 255).Select(i => {
string s = i.ToString("X2");
return ((uint)s[0]) + ((uint)s[1] << 16);
}).ToArray();
static string ByteArrayToHexViaLookupPerByte(byte[] bytes) {
var result = new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
var val = _Lookup32[bytes[i]];
result[2*i] = (char)val;
result[2*i + 1] = (char) (val >> 16);
}
return new string(result);
}
static string ByteArrayToHexViaLookup(byte[] bytes) {
string[] hexStringTable = new string[] {
"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F",
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F",
"20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F",
"30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F",
"40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F",
"50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F",
"60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F",
"70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F",
"80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F",
"90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F",
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF",
"B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF",
"C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF",
"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF",
"E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF",
"F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF",
};
StringBuilder result = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes) {
result.Append(hexStringTable[b]);
}
return result.ToString();
}
Dodano odpowiedź Waleeda do analizy. Dosyć szybko.
Dodano string.Concat
Array.ConvertAll
wariant kompletności (wymaga .NET 4.0). Na równi z string.Join
wersją.
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, ale BitConverter
wciąż wygrywa.
Dodano SoapHexBinary
odpowiedź Mykroft do analizy, która zajęła trzecie miejsce.
Dodano odpowiedź manipulacji bajtami CodesInChaos, która zajęła pierwsze miejsce (z dużym marginesem na dużych blokach tekstu).
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).
Dodano nową odpowiedź bajtową @ CodesInChaos. Wydaje się, że przejął inicjatywę zarówno w testach zdań, jak i testach pełnotekstowych.
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.
bytes.ToHexStringAtLudicrousSpeed()
.).
Istnieje klasa SoapHexBinary, która robi dokładnie to, co chcesz.
using System.Runtime.Remoting.Metadata.W3cXsd2001;
public static byte[] GetStringToBytes(string value)
{
SoapHexBinary shb = SoapHexBinary.Parse(value);
return shb.Value;
}
public static string GetBytesToString(byte[] value)
{
SoapHexBinary shb = new SoapHexBinary(value);
return shb.ToString();
}
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.
static string ByteToHexBitFiddle(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
int b;
for (int i = 0; i < bytes.Length; i++) {
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b-10)>>31)&-7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string(c);
}
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn
Porzucić wszelką nadzieję wy, którzy wchodzą tutaj
Wyjaśnienie tego dziwnego bicia:
bytes[i] >> 4
wyodrębnia wysoką część bajtu bytes[i] & 0xF
wyodrębnia niską wartość części bajtub - 10
< 0
dla wartości b < 10
, które staną się cyfrą dziesiętną >= 0
dla wartości b > 10
, które staną się literą od A
do F
.i >> 31
32-bitowej liczby całkowitej ze znakiem powoduje wyodrębnienie znaku dzięki rozszerzeniu znaku. Będzie -1
za i < 0
i 0
za i >= 0
.(b-10)>>31
będą to 0
litery i -1
cyfry.0
, a b
mieści się w zakresie od 10 do 15. Chcemy mapować go do A
(65) do F
(70), co oznacza dodanie 55 ( 'A'-10
).b
w zakresie od 0 do 9 do zakresu 0
(48) do 9
(57). Oznacza to, że musi być -7 ( '0' - 55
). & -7
od (0 & -7) == 0
i (-1 & -7) == -7
.Kilka dalszych uwag:
c
, ponieważ pomiar pokazuje, że obliczenie jej na podstawie i
jest tańsze.i < bytes.Length
jako górnej granicy pętli pozwala JITterowi na wyeliminowanie sprawdzania granic bytes[i]
, więc wybrałem ten wariant.b
int pozwala na niepotrzebne konwersje zi do bajtu.hex string
do byte[] array
?
87 + b + (((b-10)>>31)&-39)
byte[] array
”, co dosłownie oznacza tablicę tablic bajtowych lub byte[][]
. 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:
String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));
Lub, jeśli używasz .NET 4.0:
String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));
(To ostatnie z komentarza do oryginalnego postu.)
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.
private static readonly uint[] _lookup32 = CreateLookup32();
private static uint[] CreateLookup32()
{
var result = new uint[256];
for (int i = 0; i < 256; i++)
{
string s=i.ToString("X2");
result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
}
return result;
}
private static string ByteArrayToHexViaLookup32(byte[] bytes)
{
var lookup32 = _lookup32;
var result = new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
var val = lookup32[bytes[i]];
result[2*i] = (char)val;
result[2*i + 1] = (char) (val >> 16);
}
return new string(result);
}
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:
private static readonly uint[] _lookup32Unsafe = CreateLookup32Unsafe();
private static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();
private static uint[] CreateLookup32Unsafe()
{
var result = new uint[256];
for (int i = 0; i < 256; i++)
{
string s=i.ToString("X2");
if(BitConverter.IsLittleEndian)
result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
else
result[i] = ((uint)s[1]) + ((uint)s[0] << 16);
}
return result;
}
public static string ByteArrayToHexViaLookup32Unsafe(byte[] bytes)
{
var lookupP = _lookup32UnsafeP;
var result = new char[bytes.Length * 2];
fixed(byte* bytesP = bytes)
fixed (char* resultP = result)
{
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++)
{
resultP2[i] = lookupP[bytesP[i]];
}
}
return new string(result);
}
Lub jeśli uważasz, że dopuszczalne jest zapisanie bezpośrednio w ciągu:
public static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes)
{
var lookupP = _lookup32UnsafeP;
var result = new string((char)0, bytes.Length * 2);
fixed (byte* bytesP = bytes)
fixed (char* resultP = result)
{
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++)
{
resultP2[i] = lookupP[bytesP[i]];
}
}
return result;
}
Span
można go teraz używać zamiast unsafe
??
Możesz użyć metody BitConverter.ToString:
byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256}
Console.WriteLine( BitConverter.ToString(bytes));
Wynik:
00-01-02-04-08-10-20-40-80-FF
Więcej informacji: BitConverter.ToString Method (Byte [])
Właśnie dzisiaj spotkałem ten sam problem i natknąłem się na ten kod:
private static string ByteArrayToHex(byte[] barray)
{
char[] c = new char[barray.Length * 2];
byte b;
for (int i = 0; i < barray.Length; ++i)
{
b = ((byte)(barray[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
Ź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).
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
i String.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 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:
public static byte[] HexadecimalStringToByteArray_Original(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
for (var i = 0; i < outputLength; i++)
output[i] = Convert.ToByte(input.Substring(i * 2, 2), 16);
return output;
}
Wersja 4:
public static byte[] HexadecimalStringToByteArray_Rev4(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
output[i] = Convert.ToByte(new string(new char[2] { (char)sr.Read(), (char)sr.Read() }), 16);
}
return output;
}
Wersja unika String.Substring
i używa StringReader
zamiast niej. Podany powód to:
Edycja: możesz poprawić wydajność długich ciągów znaków, używając parsera jednoprzebiegowego, na przykład:
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ąć.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
{
numeral[0] = (char)sr.Read();
numeral[1] = (char)sr.Read();
output[i] = Convert.ToByte(new string(numeral), 16);
}
}
return output;
}
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.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
{
var read = sr.Read(numeral, 0, 2);
Debug.Assert(read == 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
}
return output;
}
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.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
for (int i = 0, j = 0; i < outputLength; i++, j += 2)
{
input.CopyTo(j, numeral, 0, 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
return output;
}
Czy naprawdę potrzebujesz j
indeksu, który zwiększa się w krokach po dwa równolegle i
? Oczywiście nie, wystarczy pomnożyć i
przez dwa (które kompilator powinien być w stanie zoptymalizować do dodatku).
public static byte[] HexadecimalStringToByteArray_BestEffort(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
for (int i = 0; i < outputLength; i++)
{
input.CopyTo(i * 2, numeral, 0, 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
return output;
}
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, ale String.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żywany CopyTo
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 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.ToByte
i String.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ę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.60GHz
Cores: 8
Current Clock Speed: 2600
Max Clock Speed: 2600
--------------------
Parsing hexadecimal string into an array of bytes
--------------------
HexadecimalStringToByteArray_Original: 7,777.09 average ticks (over 10000 runs), 1.2X
HexadecimalStringToByteArray_BestEffort: 8,550.82 average ticks (over 10000 runs), 1.1X
HexadecimalStringToByteArray_Rev4: 9,218.03 average ticks (over 10000 runs), 1.0X
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.
209113288F93A9AB8E474EA78D899AFDBB874355
Baw się dobrze! (Ale optymalizuj z umiarem).
Uzupełnienie odpowiedzi przez @CodesInChaos (metoda odwrócona)
public static byte[] HexToByteUsingByteManipulation(string s)
{
byte[] bytes = new byte[s.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
int hi = s[i*2] - 65;
hi = hi + 10 + ((hi >> 31) & 7);
int lo = s[i*2 + 1] - 65;
lo = lo + 10 + ((lo >> 31) & 7) & 0x0f;
bytes[i] = (byte) (lo | hi << 4);
}
return bytes;
}
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 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...
).
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.
private static readonly byte[] LookupTable = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static byte Lookup(char c)
{
var b = LookupTable[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
public static byte ToByte(char[] chars, int offset)
{
return (byte)(Lookup(chars[offset]) << 4 | Lookup(chars[offset + 1]));
}
private static readonly char[][] LookupTableUpper;
private static readonly char[][] LookupTableLower;
static Hex()
{
LookupTableLower = new char[256][];
LookupTableUpper = new char[256][];
for (var i = 0; i < 256; i++)
{
LookupTableLower[i] = i.ToString("x2").ToCharArray();
LookupTableUpper[i] = i.ToString("X2").ToCharArray();
}
}
public static char[] ToCharLower(byte[] b, int bOffset)
{
return LookupTableLower[b[bOffset]];
}
public static char[] ToCharUpper(byte[] b, int bOffset)
{
return LookupTableUpper[b[bOffset]];
}
StringBuilderToStringFromBytes: 106148
BitConverterToStringFromBytes: 15783
ArrayConvertAllToStringFromBytes: 54290
ByteManipulationToCharArray: 8444
TableBasedToCharArray: 5651 *
* to rozwiązanie
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.
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.
private byte[] HexStringToByteArray(string hexString)
{
int hexStringLength = hexString.Length;
byte[] b = new byte[hexStringLength / 2];
for (int i = 0; i < hexStringLength; i += 2)
{
int topChar = (hexString[i] > 0x40 ? hexString[i] - 0x37 : hexString[i] - 0x30) << 4;
int bottomChar = hexString[i + 1] > 0x40 ? hexString[i + 1] - 0x37 : hexString[i + 1] - 0x30;
b[i / 2] = Convert.ToByte(topChar + bottomChar);
}
return b;
}
hexString[i] &= ~0x20;
Po co to komplikować? Jest to proste w Visual Studio 2008:
DO#:
string hex = BitConverter.ToString(YourByteArray).Replace("-", "");
VB:
Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
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:
04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939f
Time to parse 100,000 times: 50.4192 ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Accepted answer: (StringToByteArray)
Time to parse 100000 times: 233.1264ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With Mono's implementation:
Time to parse 100000 times: 777.2544ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With SoapHexBinary:
Time to parse 100000 times: 845.1456ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Linie base64 i „BitConverter'd” służą do testowania poprawności. Zauważ, że są równe.
Implementacja:
public static byte[] ToByteArrayFromHex(string hexString)
{
if (hexString.Length % 2 != 0) throw new ArgumentException("String must have an even length");
var array = new byte[hexString.Length / 2];
for (int i = 0; i < hexString.Length; i += 2)
{
array[i/2] = ByteFromTwoChars(hexString[i], hexString[i + 1]);
}
return array;
}
private static byte ByteFromTwoChars(char p, char p_2)
{
byte ret;
if (p <= '9' && p >= '0')
{
ret = (byte) ((p - '0') << 4);
}
else if (p <= 'f' && p >= 'a')
{
ret = (byte) ((p - 'a' + 10) << 4);
}
else if (p <= 'F' && p >= 'A')
{
ret = (byte) ((p - 'A' + 10) << 4);
} else throw new ArgumentException("Char is not a hex digit: " + p,"p");
if (p_2 <= '9' && p_2 >= '0')
{
ret |= (byte) ((p_2 - '0'));
}
else if (p_2 <= 'f' && p_2 >= 'a')
{
ret |= (byte) ((p_2 - 'a' + 10));
}
else if (p_2 <= 'F' && p_2 >= 'A')
{
ret |= (byte) ((p_2 - 'A' + 10));
} else throw new ArgumentException("Char is not a hex digit: " + p_2, "p_2");
return ret;
}
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ź.)
Bezpieczne wersje:
public static class HexHelper
{
[System.Diagnostics.Contracts.Pure]
public static string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string hexAlphabet = @"0123456789ABCDEF";
var chars = new char[checked(value.Length * 2)];
unchecked
{
for (int i = 0; i < value.Length; i++)
{
chars[i * 2] = hexAlphabet[value[i] >> 4];
chars[i * 2 + 1] = hexAlphabet[value[i] & 0xF];
}
}
return new string(chars);
}
[System.Diagnostics.Contracts.Pure]
public static byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = value[i * 2]; // High 4 bits.
int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
b = value[i * 2 + 1]; // Low 4 bits.
val += (b - '0') + ((('9' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
return result;
}
}
}
Niebezpieczne wersje Dla tych, którzy wolą wydajność i nie boją się niepewności. O 35% szybszy ToHex i 10% szybszy FromHex.
public static class HexUnsafeHelper
{
[System.Diagnostics.Contracts.Pure]
public static unsafe string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string alphabet = @"0123456789ABCDEF";
string result = new string(' ', checked(value.Length * 2));
fixed (char* alphabetPtr = alphabet)
fixed (char* resultPtr = result)
{
char* ptr = resultPtr;
unchecked
{
for (int i = 0; i < value.Length; i++)
{
*ptr++ = *(alphabetPtr + (value[i] >> 4));
*ptr++ = *(alphabetPtr + (value[i] & 0xF));
}
}
}
return result;
}
[System.Diagnostics.Contracts.Pure]
public static unsafe byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
fixed (char* valuePtr = value)
{
char* valPtr = valuePtr;
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = *valPtr++; // High 4 bits.
int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
b = *valPtr++; // Low 4 bits.
val += (b - '0') + ((('9' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
}
return result;
}
}
}
BTW 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):
public static byte[] HexToBytes(this string hexString)
{
byte[] b = new byte[hexString.Length / 2];
char c;
for (int i = 0; i < hexString.Length / 2; i++)
{
c = hexString[i * 2];
b[i] = (byte)((c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57)) << 4);
c = hexString[i * 2 + 1];
b[i] += (byte)(c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57));
}
return b;
}
Funkcja Waleed Eissa z obsługą małych liter:
public static string BytesToHex(this byte[] barray, bool toLowerCase = true)
{
byte addByte = 0x37;
if (toLowerCase) addByte = 0x57;
char[] c = new char[barray.Length * 2];
byte b;
for (int i = 0; i < barray.Length; ++i)
{
b = ((byte)(barray[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + addByte : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + addByte : b + 0x30);
}
return new string(c);
}
Metody rozszerzenia (zrzeczenie się: kod całkowicie nieprzetestowany, BTW ...):
public static class ByteExtensions
{
public static string ToHexString(this byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
{
hex.AppendFormat("{0:x2}", b);
}
return hex.ToString();
}
}
itd. Użyj jednego z trzech rozwiązań Tomalaka (ostatnie z nich to metoda rozszerzenia ciągu).
Od programistów Microsoftu, przyjemna, prosta konwersja:
public static string ByteArrayToString(byte[] ba)
{
// Concatenate the bytes into one long string
return ba.Aggregate(new StringBuilder(32),
(sb, b) => sb.Append(b.ToString("X2"))
).ToString();
}
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 :
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
for(int i=0; i < ba.Length; i++) // <-- Use for loop is faster than foreach
hex.Append(ba[i].ToString("X2")); // <-- ToString is faster than AppendFormat
return hex.ToString();
}
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.
b.ToSting("X2")
.
I do wstawiania do ciągu SQL (jeśli nie używasz parametrów polecenia):
public static String ByteArrayToSQLHexString(byte[] Source)
{
return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
Source == null
lub Source.Length == 0
mamy problem, proszę pana!
Pod względem prędkości wydaje się to lepsze niż cokolwiek tutaj:
public static string ToHexString(byte[] data) {
byte b;
int i, j, k;
int l = data.Length;
char[] r = new char[l * 2];
for (i = 0, j = 0; i < l; ++i) {
b = data[i];
k = b >> 4;
r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
k = b & 15;
r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
}
return new string(r);
}
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.
public static byte[] StringToByteArray2(string hex)
{
byte[] bytes = new byte[hex.Length/2];
int bl = bytes.Length;
for (int i = 0; i < bl; ++i)
{
bytes[i] = (byte)((hex[2 * i] > 'F' ? hex[2 * i] - 0x57 : hex[2 * i] > '9' ? hex[2 * i] - 0x37 : hex[2 * i] - 0x30) << 4);
bytes[i] |= (byte)(hex[2 * i + 1] > 'F' ? hex[2 * i + 1] - 0x57 : hex[2 * i + 1] > '9' ? hex[2 * i + 1] - 0x37 : hex[2 * i + 1] - 0x30);
}
return bytes;
}
Ta wersja ByteArrayToHexViaByteManipulation może być szybsza.
Z moich raportów:
...
static private readonly char[] hexAlphabet = new char[]
{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
static string ByteArrayToHexViaByteManipulation3(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
byte b;
for (int i = 0; i < bytes.Length; i++)
{
b = ((byte)(bytes[i] >> 4));
c[i * 2] = hexAlphabet[b];
b = ((byte)(bytes[i] & 0xF));
c[i * 2 + 1] = hexAlphabet[b];
}
return new string(c);
}
Myślę, że ten jest optymalizacją:
static private readonly char[] hexAlphabet = new char[]
{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
static string ByteArrayToHexViaByteManipulation4(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
for (int i = 0, ptr = 0; i < bytes.Length; i++, ptr += 2)
{
byte b = bytes[i];
c[ptr] = hexAlphabet[b >> 4];
c[ptr + 1] = hexAlphabet[b & 0xF];
}
return new string(c);
}
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.
public static String ToHex (byte[] data)
{
int dataLength = data.Length;
// pre-create the stringbuilder using the length of the data * 2, precisely enough
StringBuilder sb = new StringBuilder (dataLength * 2);
for (int i = 0; i < dataLength; i++) {
int b = data [i];
// check using calculation over bits to see if first tuple is a letter
// isLetter is zero if it is a digit, 1 if it is a letter
int isLetter = (b >> 7) & ((b >> 6) | (b >> 5)) & 1;
// calculate the code using a multiplication to make up the difference between
// a digit character and an alphanumerical character
int code = '0' + ((b >> 4) & 0xF) + isLetter * ('A' - '9' - 1);
// now append the result, after casting the code point to a character
sb.Append ((Char)code);
// do the same with the lower (less significant) tuple
isLetter = (b >> 3) & ((b >> 2) | (b >> 1)) & 1;
code = '0' + (b & 0xF) + isLetter * ('A' - '9' - 1);
sb.Append ((Char)code);
}
return sb.ToString ();
}
public static byte[] FromHex (String hex)
{
// pre-create the array
int resultLength = hex.Length / 2;
byte[] result = new byte[resultLength];
// set validity = 0 (0 = valid, anything else is not valid)
int validity = 0;
int c, isLetter, value, validDigitStruct, validDigit, validLetterStruct, validLetter;
for (int i = 0, hexOffset = 0; i < resultLength; i++, hexOffset += 2) {
c = hex [hexOffset];
// check using calculation over bits to see if first char is a letter
// isLetter is zero if it is a digit, 1 if it is a letter (upper & lowercase)
isLetter = (c >> 6) & 1;
// calculate the tuple value using a multiplication to make up the difference between
// a digit character and an alphanumerical character
// minus 1 for the fact that the letters are not zero based
value = ((c & 0xF) + isLetter * (-1 + 10)) << 4;
// check validity of all the other bits
validity |= c >> 7; // changed to >>, maybe not OK, use UInt?
validDigitStruct = (c & 0x30) ^ 0x30;
validDigit = ((c & 0x8) >> 3) * (c & 0x6);
validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);
validLetterStruct = c & 0x18;
validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
validity |= isLetter * (validLetterStruct | validLetter);
// do the same with the lower (less significant) tuple
c = hex [hexOffset + 1];
isLetter = (c >> 6) & 1;
value ^= (c & 0xF) + isLetter * (-1 + 10);
result [i] = (byte)value;
// check validity of all the other bits
validity |= c >> 7; // changed to >>, maybe not OK, use UInt?
validDigitStruct = (c & 0x30) ^ 0x30;
validDigit = ((c & 0x8) >> 3) * (c & 0x6);
validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);
validLetterStruct = c & 0x18;
validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
validity |= isLetter * (validLetterStruct | validLetter);
}
if (validity != 0) {
throw new ArgumentException ("Hexadecimal encoding incorrect for input " + hex);
}
return result;
}
Konwertowane z kodu Java.
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++
i offset
zamiast offset
i offset + 1
może dawać jakieś teoretyczne korzyści, ale podejrzewam, że kompilator radzi sobie z tym lepiej niż ja.
private static readonly byte[] LookupTableLow = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static readonly byte[] LookupTableHigh = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static byte LookupLow(char c)
{
var b = LookupTableLow[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
private static byte LookupHigh(char c)
{
var b = LookupTableHigh[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
public static byte ToByte(char[] chars, int offset)
{
return (byte)(LookupHigh(chars[offset++]) | LookupLow(chars[offset]));
}
Jest to tuż przy mojej głowie i nie zostało przetestowane ani przetestowane.
Kolejna odmiana różnorodności:
public static byte[] FromHexString(string src)
{
if (String.IsNullOrEmpty(src))
return null;
int index = src.Length;
int sz = index / 2;
if (sz <= 0)
return null;
byte[] rc = new byte[sz];
while (--sz >= 0)
{
char lo = src[--index];
char hi = src[--index];
rc[sz] = (byte)(
(
(hi >= '0' && hi <= '9') ? hi - '0' :
(hi >= 'a' && hi <= 'f') ? hi - 'a' + 10 :
(hi >= 'A' && hi <= 'F') ? hi - 'A' + 10 :
0
)
<< 4 |
(
(lo >= '0' && lo <= '9') ? lo - '0' :
(lo >= 'a' && lo <= 'f') ? lo - 'a' + 10 :
(lo >= 'A' && lo <= 'F') ? lo - 'A' + 10 :
0
)
);
}
return rc;
}
Niezoptymalizowany pod kątem szybkości, ale bardziej LINQy niż większość odpowiedzi (.NET 4.0):
<Extension()>
Public Function FromHexToByteArray(hex As String) As Byte()
hex = If(hex, String.Empty)
If hex.Length Mod 2 = 1 Then hex = "0" & hex
Return Enumerable.Range(0, hex.Length \ 2).Select(Function(i) Convert.ToByte(hex.Substring(i * 2, 2), 16)).ToArray
End Function
<Extension()>
Public Function ToHexString(bytes As IEnumerable(Of Byte)) As String
Return String.Concat(bytes.Select(Function(b) b.ToString("X2")))
End Function
Dwa mashupy, które składają dwie operacje skubania w jedną.
Prawdopodobnie całkiem wydajna wersja:
public static string ByteArrayToString2(byte[] ba)
{
char[] c = new char[ba.Length * 2];
for( int i = 0; i < ba.Length * 2; ++i)
{
byte b = (byte)((ba[i>>1] >> 4*((i&1)^1)) & 0xF);
c[i] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string( c );
}
Dekadencka wersja linq-with-bit-hacking:
public static string ByteArrayToString(byte[] ba)
{
return string.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) );
}
I odwrotnie:
public static byte[] HexStringToByteArray( string s )
{
byte[] ab = new byte[s.Length>>1];
for( int i = 0; i < s.Length; i++ )
{
int b = s[i];
b = (b - '0') + ((('9' - b)>>31)&-7);
ab[i>>1] |= (byte)(b << 4*((i&1)^1));
}
return ab;
}
Innym sposobem jest stackalloc
zmniejszenie ciśnienia w pamięci GC:
static string ByteToHexBitFiddle(byte[] bytes)
{
var c = stackalloc char[bytes.Length * 2 + 1];
int b;
for (int i = 0; i < bytes.Length; ++i)
{
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
}
c[bytes.Length * 2 ] = '\0';
return new string(c);
}
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:
using System;
namespace ConversionExtensions
{
public static class ByteArrayExtensions
{
private readonly static char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
public static string ToHexString(this byte[] bytes)
{
char[] hex = new char[bytes.Length * 2];
int index = 0;
foreach (byte b in bytes)
{
hex[index++] = digits[b >> 4];
hex[index++] = digits[b & 0x0F];
}
return new string(hex);
}
}
}
using System;
using System.IO;
namespace ConversionExtensions
{
public static class StringExtensions
{
public static byte[] ToBytes(this string hexString)
{
if (!string.IsNullOrEmpty(hexString) && hexString.Length % 2 != 0)
{
throw new FormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");
}
hexString = hexString.ToUpperInvariant();
byte[] data = new byte[hexString.Length / 2];
for (int index = 0; index < hexString.Length; index += 2)
{
int highDigitValue = hexString[index] <= '9' ? hexString[index] - '0' : hexString[index] - 'A' + 10;
int lowDigitValue = hexString[index + 1] <= '9' ? hexString[index + 1] - '0' : hexString[index + 1] - 'A' + 10;
if (highDigitValue < 0 || lowDigitValue < 0 || highDigitValue > 15 || lowDigitValue > 15)
{
throw new FormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");
}
else
{
byte value = (byte)((highDigitValue << 4) | (lowDigitValue & 0x0F));
data[index / 2] = value;
}
}
return data;
}
}
}
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.
Procesor Intel Pentium III Xeon
Cores: 4 <br/> Current Clock Speed: 1576 <br/> Max Clock Speed: 3092 <br/>
Konwersja tablicy bajtów na reprezentację ciągu szesnastkowego
ByteArrayToHexViaByteManipulation2: 39 366,64 średnich tyknięć (ponad 1000 przebiegów), 22,4X
ByteArrayToHexViaOptimizedLookupAndShift: 41 588,64 średnich tyknięć (ponad 1000 przebiegów), 21,2X
ByteArrayToHexViaLookup: 55.509.56 średnich tyknięć (ponad 1000 przebiegów), 15,9X
ByteArrayToHexViaByteManipulation: 65 349,12 średnich tyknięć (ponad 1000 przebiegów), 13,5X
ByteArrayToHexViaLookupAndShift: 86.926.87 średnich tyknięć (ponad 1000 przebiegów), 10.2X
ByteArrayToHexStringViaBitConverter: 139 353,73 średnich tyknięć (ponad 1000 przebiegów), 6,3X
ByteArrayToHexViaSoapHexBinary: 314 588,77 średnich tyknięć (ponad 1000 przebiegów), 2,8X
ByteArrayToHexStringViaStringBuilderForEachByteToString: 344 264,64% średnich tyknięć (ponad 1000 przebiegów), 2,6X
ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382 623,44 średnich tyknięć (ponad 1000 przebiegów), 2,3X
ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 888 111,95 średnich tyknięć (ponad 1000 przebiegów), 1,1X
ByteArrayToHexStringViaStringConcatArrayConvertAll: 839 244,84 średnie tiki (ponad 1000 przebiegów), 1,1X
ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867 303,98 średnich tyknięć (ponad 1000 przebiegów), 1,0X
ByteArrayToHexStringViaStringJoinArrayConvertAll: 882,710.28 średnich tyknięć (ponad 1000 przebiegów), 1,0X
Kolejna szybka funkcja ...
private static readonly byte[] HexNibble = new byte[] {
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
};
public static byte[] HexStringToByteArray( string str )
{
int byteCount = str.Length >> 1;
byte[] result = new byte[byteCount + (str.Length & 1)];
for( int i = 0; i < byteCount; i++ )
result[i] = (byte) (HexNibble[str[i << 1] - 48] << 4 | HexNibble[str[(i << 1) + 1] - 48]);
if( (str.Length & 1) != 0 )
result[byteCount] = (byte) HexNibble[str[str.Length - 1] - 48];
return result;
}