Jak przekonwertować tablicę bajtów na ciąg szesnastkowy i odwrotnie?

1371

Jak przekonwertować tablicę bajtów na ciąg szesnastkowy i odwrotnie?

alextansc
źródło
8
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:

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 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.

Tomalak
źródło
24
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ć.

Wyniki

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ą.

  1. Dodaj nową metodę statyczną ( Func<byte[], string>) do /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Dodaj nazwę tej metody do TestCandidateswartości zwracanej w tej samej klasie.
  3. Upewnij się, że korzystasz z żądanej wersji wejściowej, zdania lub tekstu, przełączając komentarze GenerateTestInputw tej samej klasie.
  4. Naciśnij F5i poczekaj na wynik (zrzut HTML jest również generowany w folderze / bin).
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();
}

Aktualizacja (13.01.2010)

Dodano odpowiedź Waleeda do analizy. Dosyć szybko.

Aktualizacja (2011-10-05)

Dodano string.Concat Array.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.

kaszka
źródło
Czy zechciałbyś przetestować kod z odpowiedzi Waleeda? Wydaje się być bardzo szybki. stackoverflow.com/questions/311165/…
Cristian Diaconescu
5
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;

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();
}
Mykroft
źródło
35
SoapHexBinary jest dostępny z .NET 1.0 i jest w mscorlib. Pomimo śmiesznej przestrzeni nazw, robi dokładnie to, o co pytano.
Sly Gryphon
4
Świetne znalezisko! Zauważ, że będziesz musiał uzupełniać nieparzyste ciągi wiodącym 0 dla GetStringToBytes, podobnie jak inne rozwiązanie.
Carter Medlin
Czy widziałeś myśl o wdrożeniu? Przyjęta odpowiedź ma lepszą IMHO.
mfloryan
6
Ciekawe, aby zobaczyć implementację Mono tutaj: github.com/mono/mono/blob/master/mcs/class/corlib/…
Jeremy
1
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.

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:

  1. bytes[i] >> 4wyodrębnia wysoką część bajtu
    bytes[i] & 0xFwyodrębnia niską wartość części bajtu
  2. 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.
  3. 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.
  4. Łączenie 2) i 3) pokazuje, że (b-10)>>31będą to 0litery i -1cyfry.
  5. 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).
  6. 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.
CodesInChaos
źródło
10
I hex stringdo byte[] array?
AaA
15
+1 za prawidłowe cytowanie źródła po wywołaniu odrobiny czarnej magii. Cały grad Cthulhu.
Edward
4
Co z ciągiem do bajtu []?
Syaiful Nizam Yahya
9
Miły! Dla tych, którzy potrzebują małych liter, wyrażenie oczywiście zmienia się na87 + b + (((b-10)>>31)&-39)
eXavier
2
@ AAA Powiedziałeś „ byte[] array”, co dosłownie oznacza tablicę tablic bajtowych lub byte[][]. Po prostu się naśmiewałam.
CoolOppo,
97

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.)

Will Dean
źródło
21
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.

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 unsaferodzeń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;
}
CodesInChaos
źródło
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??
Konrad
64

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 [])

Baget
źródło
14
Odpowiada tylko na połowę pytania.
Sly Gryphon
3
Gdzie jest druga część odpowiedzi?
Sawan,
56

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).

Waleed Eissa
źródło
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 []?
jjxtra
19

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.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:

    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.Substringi używa StringReaderzamiast 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 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.

    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ć ( 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.

    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 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).

    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.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.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).

tne
źródło
error: {"Nie można znaleźć żadnych rozpoznawalnych cyfr."}
Priya Jagtap
17

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 0za pomocą & 0x0f.

65 to kod 'A'

48 to kod '0'

7 to liczba liter między '9'iw 'A'tabeli ASCII ( ...456789:;<=>?@ABCD...).

CoperNick
źródło
16

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

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]));
}

Kodowanie

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]];
}

Porównanie

StringBuilderToStringFromBytes:   106148
BitConverterToStringFromBytes:     15783
ArrayConvertAllToStringFromBytes:  54290
ByteManipulationToCharArray:        8444
TableBasedToCharArray:              5651 *

* 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.

drphrozen
źródło
2
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.

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;
}
Chris F.
źródło
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:

DO#:

string hex = BitConverter.ToString(YourByteArray).Replace("-", "");

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Craig Poulton
źródło
2
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:
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 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ź.)

Ben Mosher
źródło
1
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:

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).

rev Maratius
źródło
5

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);
    }
Geografia
źródło
4

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).

Pure.Krome
źródło
Prawdopodobnie powinieneś przetestować kod, zanim zaoferujesz go na takie pytanie.
jww
3

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.

Mark
źródło
2
Iterator nie jest głównym problemem tego kodu. Powinieneś przeprowadzić test porównawczy b.ToSting("X2").
dolmen
2

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("-", "");
}
Jack Straw
źródło
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:

  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);
  }
Aleksiej Borzenkow
źródło
2

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;
}
Fredrik Hu
źródło
2

Ta wersja ByteArrayToHexViaByteManipulation może być szybsza.

Z moich raportów:

  • ByteArrayToHexViaByteManipulation3: 1,68 średnich tyknięć (ponad 1000 przebiegów), 17,5X
  • ByteArrayToHexViaByteManipulation2: 1,73 średnie tyknięcia (ponad 1000 przebiegów), 16,9X
  • ByteArrayToHexViaByteManipulation: 2,90 średnich tyknięć (ponad 1000 przebiegów), 10,1X
  • ByteArrayToHexViaLookupAndShift: 3,22 średnie tiki (ponad 1000 przebiegów), 9,1X
  • ...

    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);
    }
JoseH
źródło
2

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.

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.

Maarten Bodewes
źródło
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.

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.

ClausAndersen
źródło
1

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;          
}
Stas Makutin
źródło
1

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
MCattle
źródło
1

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;
}
JJJ
źródło
1
HexStringToByteArray („09”) zwraca 0x02, co jest złe
CoperNick
1

Innym sposobem jest stackalloczmniejszenie 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);
}
Kel
źródło
1

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:

  1. 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.
  2. 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
{
    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


JamieSee
źródło
1

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;
}
spacepille
źródło