Jak przekonwertować tablicę bajtów na łańcuch szesnastkowy i odwrotnie?

Jak można przekonwertować tablicę bajtów na łańcuch szesnastkowy i odwrotnie?

 1145
Author: alextansc, 2008-11-22

30 answers

Albo:

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 tego wykonania, na przykład tutaj .

Odwrotna konwersja przebiegałaby tak:

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życie Substring jest najlepszą opcją w połączeniu z Convert.ToByte. Zobacz ta odpowiedź aby uzyskać więcej informacji. Jeśli potrzebujesz lepszej wydajności, musisz unikać Convert.ToByte, zanim będziesz mógł upuścić SubString.

 1113
Author: Tomalak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-27 09:23:46

Analiza Wydajności

Uwaga: nowy lider od 2015-08-20.

Przeprowadziłem każdą z różnych metod konwersji przez prymitywne testy wydajności Stopwatch, Uruchomienie z losowym zdaniem (n=61, 1000 iteracji) i uruchomienie z tekstem projektu Gutenburg (n=1,238,957, 150 iteracji). Oto wyniki, mniej więcej od najszybszego do najwolniejszego. Wszystkie pomiary są w kleszczach (10 000 kleszczy = 1 ms) i wszystkie notatki względne są porównywane z [najwolniejszym] StringBuilder wdrożenie. Aby uzyskać użyty kod, zobacz poniżej lub repo test framework repo, gdzie teraz utrzymuję kod do uruchomienia tego.

Disclaimer

uwaga: nie polegaj na tych statystykach w żadnym konkretnym przypadku; są to po prostu przykładowe dane. Jeśli naprawdę potrzebujesz najwyższej wydajności, przetestuj te metody w środowisku reprezentatywnym dla Twoich potrzeb produkcyjnych z danymi reprezentatywnymi dla tego, co będziesz użyj.

Wyniki

Tabele wyszukiwania przejęły kontrolę nad manipulacją bajtami. Zasadniczo istnieje jakaś forma wstępnego obliczania tego, co dany nibble lub bajt będzie w hex. Następnie, gdy zgrywasz dane, po prostu poszukujesz następnej części, aby zobaczyć, jaki byłby to ciąg szesnastkowy. Wartość ta jest następnie dodawana do wynikowego ciągu wyjściowego w pewien sposób. Przez długi czas manipulacja bajtami, potencjalnie trudniejsza do odczytania przez niektórych programistów, była najlepiej podejdźcie.

Najlepszym rozwiązaniem będzie znalezienie reprezentatywnych danych i wypróbowanie ich w środowisku produkcyjnym. Jeśli masz różne ograniczenia pamięci, możesz preferować metodę z mniejszą ilością przydziałów do metody, która byłaby szybsza, ale zużywa więcej pamięci.

Kod Testowy

Zapraszam do zabawy z kodem testowym, którego użyłem. Wersja jest tu dołączona, ale możesz sklonować repo i dodać własne metody. Wyślij Zapytanie o pull request jeśli znajdziesz coś interesującego lub chcesz pomóc w ulepszeniu używanego frameworka testowego.

  1. Dodaj nową metodę statyczną (Func<byte[], string>) do /Tests/ConvertByteArrayToHexString / Test.cs.
  2. Dodaj nazwę metody do wartości zwracanej TestCandidates w tej samej klasie.
  3. upewnij się, że używasz żądanej wersji wejściowej, zdania lub tekstu, przełączając komentarze w GenerateTestInput w tej samej klasie.
  4. naciśnij F5 i poczekaj na wyjście (zrzut HTML jest również wygenerowane 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 (2010-01-13)

Dodał odpowiedź Waleeda do analizy. Dość szybko.

Aktualizacja (2011-10-05)

Dodano string.Concat Array.ConvertAll wariant dla kompletności (wymaga. NET 4.0). Na równi z wersją string.Join.

Aktualizacja (2012-02-05)

Test repo zawiera więcej wariantów, takich jak StringBuilder.Append(b.ToString("X2")). Żaden nie zakłócił wyników. foreach jest szybszy niż {IEnumerable}.Aggregate, na przykład, ale BitConverter nadal wygrywa.

Update (2012-04-03)

Dodał odpowiedź Mykrofta {[28] } do analizy, która zajęła trzecie miejsce.

Aktualizacja (2013-01-15)

Dodano odpowiedź manipulacji bajtami CodesInChaos, która zajęła pierwsze miejsce (dużym marginesem na dużych blokach tekstu).

Aktualizacja (2013-05-23)

Dodano odpowiedź wyszukiwania Nathana Moinvaziriego i wariant z bloga Briana Lamberta. Oba dość szybkie, ale nie przejmując przywództwa na maszynie testowej, której używałem (AMD Phenom 9750).

Aktualizacja (2014-07-31)

Dodano nową odpowiedź wyszukiwania opartą na bajtach @CodesInChaos. Wydaje się, że objął prowadzenie zarówno w testach zdaniowych, jak i w testach pełnotekstowych.

[[216]} aktualizacja (2015-08-20)

Dodanooptymalizacje airbreathera i Wariant unsafe do tegorepo . Jeśli chcesz grać w niebezpiecznej grze, możesz uzyskać ogromne zyski wydajności nad którymkolwiek z poprzednich najlepszych zwycięzców zarówno na krótkich strunach, jak i dużych SMS-y.

 415
Author: patridge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-05 05:15:44

Jest klasa o nazwie 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();
}
 212
Author: Mykroft,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-05 04:07:18

Podczas pisania kodu kryptograficznego powszechne jest unikanie gałęzi zależnych od danych i wyszukiwań tabel, aby upewnić się, że środowisko uruchomieniowe nie zależy od danych, ponieważ czas zależny od danych może prowadzić do ataków bocznych kanałów.

Jest też 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


Porzućcie wszelką nadzieję, wy, którzy tu wchodzicie]}

Wyjaśnienie dziwacznego wiercenia:

  1. bytes[i] >> 4 wyciąga wysoki skubanie bajtu
    bytes[i] & 0xF wyciąga małe skubanie bajtu
  2. b - 10
    jest < 0 dla wartości b < 10, które staną się cyfrą dziesiętną
    is >= 0 for values b > 10, which will be a letter from A to F.
  3. użycie i >> 31 na podpisanej 32-bitowej liczbie całkowitej wyodrębnia znak, dzięki rozszerzeniu sign. Będzie to -1 dla i < 0 i 0 dla i >= 0.
  4. połączenie 2) i 3) pokazuje, że (b-10)>>31 będzie 0 dla liter i -1 dla cyfr.
  5. Szukam w przypadku liter Ostatnie sumowanie staje się 0, a b mieści się w przedziale od 10 do 15. Chcemy odwzorować go na A (65) na F (70), co oznacza dodanie 55 ('A'-10).
  6. patrząc na przypadek cyfr, chcemy dostosować Ostatnie sumowanie tak, aby odwzorowywało b z zakresu od 0 do 9 do zakresu 0(48) do 9(57). Oznacza to, że musi stać się -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żyć & -7, ponieważ (0 & -7) == 0 i (-1 & -7) == -7.

Kilka dalszych rozważań:

  • nie użyłem drugiej zmiennej pętli do indeksowania do c, ponieważ pomiar pokazuje, że obliczanie jej z i jest tańsze.
  • użycie dokładnie i < bytes.Length jako górnej granicy pętli pozwala Jitterowi wyeliminować kontrole granic na bytes[i], więc wybrałem ten wariant.
  • Tworzenie b int pozwala na niepotrzebne konwersje z I do bajtu.
 126
Author: CodesInChaos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-24 14:27:58

Jeśli chcesz większej elastyczności niż BitConverter, ale nie chcesz tych niewyraźnych pętli w stylu lat 90., możesz to zrobić:

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")));

(ten ostatni z komentarza do oryginalnego postu.)

 84
Author: Will Dean,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-21 22:56:31

Możesz użyć Bitconvertera.Metoda ToString: {]}

byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256}
Console.WriteLine( BitConverter.ToString(bytes));

Wyjście:

00-01-02-04-08-10-20-40-80-FF

Więcej informacji: BitConverter.Metoda ToString (Bajt[])

 58
Author: Baget,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-05 04:07:56

Inne podejście oparte na tabelach wyszukiwania. Ten używa tylko jednej tabeli wyszukiwania dla każdego bajtu, zamiast tabeli wyszukiwania dla każdego bajtu.

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

Przetestowałem również warianty tego za pomocą ushort, struct{char X1, X2}, struct{byte X1, X2} w tabeli wyszukiwania.

W zależności od celu kompilacji (x86, X64) miały one w przybliżeniu taką samą wydajność lub były nieco wolniejsze od tego wariantu.


I dla jeszcze większej 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 rozważysz to dopuszczalne zapisywanie bezpośrednio w łańcuchu znaków:

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;
}
 54
Author: CodesInChaos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-21 17:00:59

Właśnie natknąłem się dzisiaj na 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 na Forumbyte [] Array to Hex String (Zobacz post przez PZahra). Zmodyfikowałem trochę kod, aby usunąć prefiks 0x.

Zrobiłem kilka testów wydajności kodu i było to prawie osiem razy szybsze niż korzystanie z Bitconvertera.ToString () (najszybszy według posta patridge ' a).

 52
Author: Waleed Eissa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-21 22:58:28

Ten problem można również rozwiązać za pomocą tabeli wyszukiwania. Wymagałoby to niewielkiej ilości pamięci statycznej zarówno dla enkodera, jak i dekodera. Metoda ta będzie jednak szybka:

  • tabela kodera 512 bajtów lub 1024 bajtów (dwukrotnie rozmiar, jeśli zarówno duże, jak i małe litery jest potrzebne)
  • tabela dekodera 256 bajtów lub 64 KiB (albo pojedynczy znak lub dual char look-up)

Moje rozwiązanie używa 1024 bajtów dla tabeli kodowania i 256 bajtów dla dekodowanie.

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

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ć IOException i IndexOutOfRangeException (jeśli znak ma zbyt wysoką wartość > 256). Należy zaimplementować metody dla strumieni de/kodowania lub tablic, jest to tylko dowód koncepcji.

 15
Author: drphrozen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-21 23:34:38

Jest to odpowiedź na rewizję 4 Z bardzo popularnej odpowiedzi Tomalaka (i kolejne edycje).

Przedstawię sprawę, że ta edycja jest zła i wyjaśnię, dlaczego można ją cofnąć. Po drodze, można dowiedzieć się coś o niektórych wewnętrznych, i zobaczyć kolejny przykład tego, co przedwczesna optymalizacja naprawdę jest i jak może cię ugryźć.

Tl; dr: Po prostu użyj Convert.ToByte i String.Substring jeśli się spieszysz ("oryginalny kod" poniżej), jest najlepszy kombinacja, jeśli nie chcesz ponownie zaimplementować Convert.ToByte. Użyj czegoś bardziej zaawansowanego (zobacz inne odpowiedzi), które nie używa 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.

Warning: ta odpowiedź może stać się nieaktualna jeśli a Convert.ToByte(char[], Int32) przeciążenie jest zaimplementowane w frameworku. Jest to mało prawdopodobne wkrótce.

ogólnie rzecz biorąc, 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 będzie bardziej dojrzały lub potrzebujesz wydajności (jeśli istnieje prawdziwa potrzeba, wtedy sprawisz czas). W międzyczasie do najprostsza rzecz, która mogłaby zadział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;
    }

Rewizja unika String.Substring i zamiast tego używa StringReader. Podany powód to:

Edit: możesz poprawić wydajność długich strun za pomocą jednego podaj parser, w ten sposób:

Cóż, patrząc na kod odniesienia dla String.Substring, jest już wyraźnie "jednoprzebiegowy", a dlaczego nie miałby być? Działa na poziomie bajtów, nie na parach zastępczych.

Przydziela jednak nowy ciąg znaków, ale i tak musisz przydzielić go do Convert.ToByte. Co więcej, rozwiązanie zawarte w rewizji przydziela kolejny obiekt na każdej iteracji( tablica dwuznakowa); można 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 za pomocą dwóch cyfr (symboli).

Ale po co dzwonić dwa razy? Wystarczy wywołać jego drugie przeciążenie i poprosić go, aby odczytał dwa znaki w tablicy dwuznakowej na raz; i zmniejszyć liczbę wywołań 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;
    }

To, co ci zostało, to czytnik łańcuchów, którego jedyną dodaną "wartością" jest indeks równoległy (wewnętrzny _pos), który mogłeś sam zadeklarować (na przykład jako j), redundantna zmienna długości (wewnętrzna _length) i redundantne odniesienie do łańcucha wejściowego (wewnętrzna _s). Innymi słowy, jest bezużyteczny.

Jeśli zastanawiasz się jak Read "czyta", wystarczy spojrzeć na kod , wystarczy wywołać String.CopyTo na łańcuch wejściowy. Reszta to tylko księgowanie kosztów, aby utrzymać wartości, których nie potrzebujemy.

Więc, usuń już czytnik łańcuchów i zadzwoń CopyTo sam; jest to prostsze, jaśniejsze i bardziej wydajne.

    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 o dwa równoległe do i? Oczywiście nie, wystarczy pomnożyć i przez dwa (które kompilator powinien być w stanie zoptymalizować do dodatek).

    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 na początku, tylko zamiast używać String.Substring do przydzielania ciągu znaków i kopiowania do niego danych, używasz tablicy pośredniczącej, do której kopiujesz cyfry szesnastkowe, a następnie samodzielnie przydzielasz łańcuch i ponownie kopiujesz dane Z Tablicy i do łańcucha (kiedy przekazujesz go w konstruktorze łańcuchów). Druga kopia może być zoptymalizowana-out, jeśli ciąg jest już w intern pool, ale wtedy String.Substring również będzie w stanie tego uniknąć w takich przypadkach.

W rzeczywistości, jeśli spojrzysz na String.Substring jeszcze raz, zobaczysz, że wykorzystuje on wewnętrzną wiedzę niskiego poziomu o tym, jak ciągi są konstruowane, aby przydzielać ciąg szybciej niż normalnie byś to zrobił, i umieszcza ten sam kod używany przez CopyTo bezpośrednio tam, aby uniknąć napowietrznych połączeń.

String.Substring

  • najgorszy przypadek: jedna szybka alokacja, jedna szybka Kopia.
  • Best-case: No allocation, no przyjąłem.

Metoda Manualna

  • najgorszy przypadek: dwie normalne alokacje, jedna normalna Kopia, jedna szybka Kopia.
  • najlepszy przypadek: jedna normalna alokacja, jedna normalna Kopia.

Wniosek? jeśli chcesz użyć Convert.ToByte(String, Int32) (ponieważ nie chcesz ponownie zaimplementować tej funkcjonalności samodzielnie), nie wydaje się, aby był sposób na pokonanie String.Substring; wszystko, co robisz, to bieganie w kółko, wymyślanie koła (tylko z nieoptymalnych materiałów).

Zauważ, że korzystanie z 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 zbadać, jak to działa poprawnie.

Gdyby było Convert.ToByte(char[], Int32), rzecz jasna byłoby inaczej (byłoby możliwe zrobić to, co opisałem powyżej i całkowicie uniknąć String).

Podejrzewam, że ludzie, którzy zgłaszają lepsze wyniki poprzez "unikanie String.Substring" również unikają Convert.ToByte(String, Int32), co naprawdę powinieneś robić, jeśli i tak potrzebuję występu. Spójrz na niezliczone inne odpowiedzi, aby odkryć wszystkie różne podejścia do tego.

Zastrzeżenie: nie dekompilowałem najnowszej wersji frameworka, aby sprawdzić, czy źródło odniesienia jest aktualne, zakładam, że tak.

Teraz wszystko brzmi dobrze i logicznie, mam nadzieję, że nawet oczywiste, jeśli udało Ci się zajść 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!

rekwizyty do kuropatwy do ramy ławki, łatwo się włamać. Użyte dane wejściowe to następujący hash 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.)
 14
Author: tne,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-05 04:10:58

Uzupełnienie odpowiedzi by @ 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 obsługiwać również małe litery

hi = hi + 10 + ((hi >> 31) & 7); jest tym samym co:

hi = ch-65 + 10 + (((ch-65) >> 31) & 7);

Dla "0".."9" jest to to samo co hi = ch - 65 + 10 + 7;, które jest hi = ch - 48 (dzieje się tak z powodu 0xffffffff & 7).

Dla "A".."F" to hi = ch - 65 + 10; (dzieje się tak z powodu 0x00000000 & 7).

Dla "a"..'f' musimy mieć duże liczby, więc musimy odjąć 32 od wersji domyślnej, tworząc kilka bitów 0 za pomocą & 0x0f.

65 to kod dla 'A'

48 jest kodem '0'

7 jest liczbą liter pomiędzy '9' a 'A' w tabeli ASCII (...456789:;<=>?@ABCD...).

 12
Author: CoperNick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-21 23:07:43

To jest świetny post. Podoba mi się rozwiązanie Waleeda. Nie przepuściłem go przez test patridge ' a, ale wydaje się, że jest dość szybki. Potrzebowaĺ 'em rĂłwnieĹź procesu odwrotnego, konwertujä ... c string hex do tablicy bajtăłw, wiÄ ™ c napisaĹ' em to jako odwrotnoĺ " Ä ‡ rozwiÄ ... zania Waleeda. Nie wiem, czy jest szybszy od oryginalnego rozwiązania Tomalaka. Ponownie, nie przeprowadziłem procesu odwrotnego przez 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;
}
 9
Author: Chris F,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-12 16:51:38

Dlaczego to skomplikowane? To proste w Visual Studio 2008:

C#:

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

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
 8
Author: Craig Poulton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-21 23:20:12

Nie chcę tu skupiać się na wielu odpowiedziach, ale znalazłem dość optymalną (~4,5 x lepszą niż zaakceptowana), prostą implementację parsera łańcuchów heksowych. Po pierwsze, wyjście z 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' są tam do testowania poprawności. Zauważ, że są równe.

Realizacja:

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

Wypróbowałem kilka rzeczy z unsafe i przesunąłem (ewidentnie zbędny) ciąg znaków do skubania if do innej metody, ale ta była najszybsza.

(Przyznam, że to odpowiedź na połowę pytania. Czułem, że konwersja string->byte[] była niedostatecznie reprezentowana, podczas gdy kąt Byte[]->string wydaje się być dobrze zakryty. Tak więc, ta odpowiedź.)
 7
Author: Ben Mosher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-21 23:10:33

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

Wersje niebezpieczne dla tych, którzy preferują wydajność i nie boją się niezgodności. O 35% szybciej ToHex i 10% szybciej Odhex.

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 Do testowania benchmarku inicjalizacja alfabetu za każdym razem, gdy wywołana funkcja konwersji jest błędna, alfabet musi być const (Dla ciągu znaków) lub tylko do odczytu statycznego (dla char[]). Wtedy konwersja bajtów [] na ciąg staje się tak szybka, jak wersje manipulacji bajtami.

I z test kursu musi być skompilowany w wydaniu (z optymalizacją) i z wyłączoną opcją debugowania "Wyłącz optymalizację JIT" (to samo dla "Włącz tylko Mój kod", jeśli kod musi być debugowany).

 5
Author: Maratius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-20 06:33:34

Funkcja odwrotna dla kodu Waleeda Eissa (Łańcuch szesnastkowy do tablicy bajtów):

    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);
    }
 4
Author: Geograph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-17 11:15:19

Extension methods (disclaimer: completely untested code, 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 (z ostatnim jest metoda rozszerzenia na łańcuchu).

 3
Author: Pure.Krome,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-21 22:51:16

Od programistów Microsoftu, ładna, 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ższy jest czysty i kompaktowy, ćpuny wydajności będą krzyczeć o tym za pomocą enumeratorów. Możesz uzyskać maksymalną wydajność dzięki ulepszonej wersji oryginalnej odpowiedzi Tomolaka:

public static string ByteArrayToString(byte[] ba)   
{   
   StringBuilder hex = new StringBuilder(ba.Length * 2);   

   for(int i=0; i < ga.Length; i++)       // <-- Use for loop is faster than foreach   
       hex.Append(ba[i].ToString("X2"));   // <-- ToString is faster than AppendFormat   

   return hex.ToString();   
} 

To najszybszy ze wszystkich procedur, jakie widziałem tutaj do tej pory. Nie wierz mi na słowo... wydajność przetestuj każdą rutynę i sprawdź jej kod CIL.

 3
Author: Mark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-21 23:22:00

Jeśli chodzi o prędkość, wydaje się to być 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);
  }
 2
Author: Alexey Borzenkov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-01 08:19:33

Nie dostałem kodu, który sugerowałeś, żeby zadziałał, Olipro. hex[i] + hex[i+1] najwyraźniej zwrócił int.

[3]}udało mi się jednak odnieść pewien sukces, biorąc kilka wskazówek z kodu Waleeda i uderzając to razem. Jest brzydki jak cholera, ale wydaje się działać i wykonuje w 1/3 czasu w porównaniu do innych według moich testów(przy użyciu mechanizmu testowania patridges). W zależności od wielkości wejścia. Zamiana ?: s aby oddzielić 0-9 najpierw pewnie dałoby nieco szybszy wynik, ponieważ tam więcej cyfr 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;
}
 2
Author: Fredrik Hu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-09 17:29:51

Ta wersja Bytearraytohexviabytemanipulacji może być szybsza.

Z moich raportów:

  • ByteArrayToHexViaByteManipulation3: 1,68 średnie kleszcze( ponad 1000 przebiegów), 17,5 X
  • ByteArrayToHexViaByteManipulation2: 1,73 średnie kleszcze( ponad 1000 biegów), 16,9 x
  • Bytearraytohexviabytemanipulacja: 2,90 średnich kleszczy( ponad 1000 przebiegów), 10,1 X
  • ByteArrayToHexViaLookupAndShift: 3,22 średnie kleszcze (ponad 1000 przejazdów), 9,1 x
  • ...

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

I myślę, że ta 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);
    }
 2
Author: JoseH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-23 07:41:58

Wezmę udział w konkursie bit fiddling, ponieważ mam odpowiedź, która również używa bit-fiddling do dekodowania szesnastkowych. Zauważ, że używanie tablic znaków może być jeszcze szybsze, ponieważ wywołanie metod StringBuilder również zajmie 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 Javy.

 2
Author: Maarten Bodewes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-20 23:38:28

Oraz do wstawiania do ciągu SQL (jeśli nie używasz parametrów poleceń):

public static String ByteArrayToSQLHexString(byte[] Source)
{
    return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
 1
Author: Jack Straw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-14 21:13:35

Kolejna odmiana dla 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;          
}
 1
Author: Stas Makutin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-16 20:33:53

Nie zoptymalizowany 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
 1
Author: MCattle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-30 23:53:04

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

Decadent linq-with-bit-hacking version:

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;
}
 1
Author: JJJ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-16 10:53:43

Innym sposobem jest użycie stackalloc do zmniejszenia ciśnienia 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);
}
 1
Author: Kel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-20 16:23:25

Oto moja szansa. Stworzyłem parę klas extension, aby rozszerzyć string i byte. W teście dużych plików wydajność jest porównywalna do manipulacji bajtami 2.

Poniższy kod dla ToHexString jest zoptymalizowaną implementacją algorytmu lookup and shift. Jest prawie identyczny jak ten autorstwa Behrooza, ale okazuje się, że do iteracji używa się foreach, a licznik jest szybszy od jawnie indeksującego for.

Jest na 2 miejscu za bajtem 2 na moim maszyna i jest bardzo czytelny kod. Następujące wyniki badań są również interesujące:

ToHexStringCharArrayWithCharArraylookup: 41,589. 69 średnie kleszcze (ponad 1000 przebiegów), 1,5 X Tohexstringcharraywithstringlookup: 50,764. 06 średnie kleszcze (ponad 1000 przebiegów), 1.2 X Tohexstringstringbuilderwithcharraylookup: 62,812. 87 średnie kleszcze( ponad 1000 przebiegów), 1.0 x

Na podstawie powyższych wyników można bezpiecznie stwierdzić, że:

  1. kary za indeksowanie w łańcuch do wykonaj wyszukiwanie VS. a tablice znaków są istotne w teście dużych plików.
  2. kary za używanie Stringbuildera o znanej pojemności vs. char tablica znanych rozmiarów do utworzenia ciągu znaków jest jeszcze bardziej znacząca.

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, gdy umieściłem mój kod w projekcie testowym @ patridge na mojej maszynie. Dodałem również test konwersji do tablicy bajtów z systemu szesnastkowego. Testy, które wykonały mój kod to Bytearraytohexviaoptimizedlookup andshift and HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte został pobrany z XXXX. HexToByteArrayViaSoapHexBinary to ta 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 szesnastkową reprezentację ciągu


ByteArrayToHexViaByteManipulation2: 39,366. 64 średnie kleszcze (ponad 1000 przebiegów), 22,4 x

ByteArrayToHexViaOptimizedLookupandshift: 41,588. 64 średnie kleszcze (ponad 1000 przebiegów), 21,2 x

ByteArrayToHexViaLookup: 55,509. 56 średnie kleszcze( ponad 1000 przebiegów), 15,9 x

Bytearraytohexviabytemanipulacja: 65,349. 12 średnich kleszczy (ponad 1000 przebiegów), 13,5 x

ByteArrayToHexViaLookupAndShift: 86,926. 87 średnie kleszcze (ponad 1000 działa), 10,2 x

ByteArrayToHexStringViaBitConverter: 139,353. 73 średnia kleszcze (ponad 1000 przebiegów), 6,3 x

ByteArrayToHexViaSoapHexBinary: 314,598. 77 średnie kleszcze( ponad 1000 przebiegów), 2.8 x

ByteArrayToHexStringViaStringBuilderforeachbytetostring: 344,264. 63 średnie kleszcze (ponad 1000 przebiegów), 2,6 x

Bytearraytohexstringviastringbuilderagregatebytetostring: 382,623.44 średnie kleszcze (ponad 1000 przebiegów), 2,3 x

ByteArrayToHexStringViaStringBuilderforeachappendformat: 818,111. 95 średnie kleszcze (ponad 1000 przebiegów), 1,1 x

ByteArrayToHexStringViaStringConcatarrayconvertall: 839,244. 84 średnia kleszcze (ponad 1000 przebiegów), 1.1 x

ByteArrayToHexStringViaStringBuilderaggregateappendformat: 867,303. 98 średnie kleszcze (ponad 1000 przebiegów), 1,0 x

ByteArrayToHexStringViaStringJoinarrayconvertall: 882,710. 28 średnia kleszcze (ponad 1000 przebiegów), 1.0 x


 1
Author: JamieSee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-21 23:05:14

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;
}
 1
Author: spacepille,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-21 23:24:36

Dla wydajności wybrałbym rozwiązanie drphrozens. Drobną optymalizacją dla dekodera może być użycie tabeli dla dowolnego znaku, aby pozbyć się "

Oczywiście oba wywołania metody są kosztowne. Jeśli na danych wejściowych lub wyjściowych zostanie wykonane jakieś sprawdzenie (może to być CRC, suma kontrolna lub cokolwiek innego), if (b == 255)... może zostać pominięte, a tym samym również wywołanie metody.

Użycie offset++ i offset zamiast offset i offset + 1 może dać 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]));
}
To jest po prostu poza czubkiem mojej głowy i nie zostało przetestowane lub benchmarked.
 1
Author: ClausAndersen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-21 23:33:22