Frage Wie konvertiert man ein Byte-Array in eine hexadezimale Zeichenfolge und umgekehrt?


Wie können Sie ein Byte-Array in eine hexadezimale Zeichenfolge konvertieren und umgekehrt?


1115
2018-03-08 21:56


Ursprung


Antworten:


Entweder:

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

oder:

public static string ByteArrayToString(byte[] ba)
{
  return BitConverter.ToString(ba).Replace("-","");
}

Es gibt zum Beispiel noch mehr Varianten Hier.

Die umgekehrte Konvertierung würde folgendermaßen aussehen:

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

Verwenden Substring ist die beste Option in Kombination mit Convert.ToByte. Sehen diese Antwort für mehr Informationen. Wenn Sie eine bessere Leistung benötigen, müssen Sie vermeiden Convert.ToByte bevor du fallen kannst SubString.


1071



Leistungsanalyse

Hinweis: neuer Leiter ab 2015-08-20.

Ich habe jede der verschiedenen Umwandlungsmethoden durch etwas Rohöl durchgeführt Stopwatch Performancetests, ein Lauf mit einem zufälligen Satz (n = 61, 1000 Iterationen) und ein Lauf mit einem Projekt Gutenburg Text (n = 1.238.957, 150 Iterationen). Hier sind die Ergebnisse, ungefähr vom schnellsten zum langsamsten. Alle Messungen sind in Ticks (10.000 Ticks = 1 ms) und alle relativen Noten werden mit den [langsamsten] verglichen StringBuilder Implementierung. Für den verwendeten Code, siehe unten oder die Test Framework Repo Wo ich jetzt den Code für das Ausführen von diesem verwalten.

Haftungsausschluss

WARNUNG: Verlassen Sie sich nicht auf diese Werte für irgendetwas Konkretes; Sie sind lediglich ein Beispiel für einen Lauf von Beispieldaten. Wenn Sie wirklich erstklassige Leistung benötigen, testen Sie diese Methoden bitte in einer Umgebung, die für Ihre Produktionsanforderungen repräsentativ ist und Daten enthält, die für Ihre Verwendung repräsentativ sind.

Ergebnisse

Nachschlagetabellen haben bei der Byte-Manipulation die Nase vorn. Grundsätzlich gibt es eine Art von Vorberechnung, was ein gegebenes Nibble oder Byte in Hex bedeutet. Wenn Sie dann durch die Daten rippen, sehen Sie einfach die nächste Portion nach, um zu sehen, welche Hex-Zeichenkette es wäre. Dieser Wert wird dann auf irgendeine Weise zu der resultierenden Zeichenfolgenausgabe hinzugefügt. Lange Zeit war die Byte-Manipulation, die von einigen Entwicklern möglicherweise schwieriger zu lesen war, der beste Ansatz.

Ihre beste Wette wird immer noch sein, repräsentative Daten zu finden und sie in einer produktionsähnlichen Umgebung auszuprobieren. Wenn Sie andere Speicherbeschränkungen haben, bevorzugen Sie möglicherweise eine Methode mit weniger Zuordnungen zu einer, die zwar schneller wäre, aber mehr Speicher verbraucht.

Code testen

Fühlen Sie sich frei, mit dem Testcode zu spielen, den ich verwendet habe. Eine Version ist hier enthalten, aber fühlen Sie sich frei, die zu klonen Repo und füge deine eigenen Methoden hinzu. Bitte senden Sie eine Pull-Anfrage, wenn Sie etwas Interessantes finden oder zur Verbesserung des Test-Frameworks beitragen möchten.

  1. Fügen Sie die neue statische Methode hinzu (Func<byte[], string>) zu /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Fügen Sie den Namen dieser Methode zu TestCandidates Rückgabewert in derselben Klasse.
  3. Stellen Sie sicher, dass Sie die gewünschte Eingabeversion, den Satz oder den Text ausführen, indem Sie die Kommentare aktivieren GenerateTestInput in derselben Klasse.
  4. Schlagen F5 und warten auf die Ausgabe (ein HTML-Dump wird auch im / bin-Ordner generiert).
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();
}

Update (13.01.2010)

Waleeds Antwort zur Analyse hinzugefügt. Ziemlich schnell.

Update (05.10.2011)

Hinzugefügt string.Concat  Array.ConvertAll Variante zur Vollständigkeit (erfordert .NET 4.0). Auf Augenhöhe mit string.Join Ausführung.

Update (05.02.2012)

Test-Repo enthält mehr Varianten wie StringBuilder.Append(b.ToString("X2")). Niemand verärgert die Ergebnisse. foreach ist schneller als {IEnumerable}.Aggregatezum Beispiel, aber BitConverter gewinnt immer noch.

Update (2012-04-03)

Mykroft's hinzugefügt SoapHexBinary Antwort auf die Analyse, die den dritten Platz übernahm.

Update (2013-01-15)

CodeInChaos 'Antwort zur Byte-Manipulation hinzugefügt, die den ersten Platz einnahm (bei großen Textblöcken mit großem Abstand).

Update (23.05.2013)

Nathan Moinvaziris Lookup-Antwort und die Variante aus Brian Lamberts Blog hinzugefügt. Beide ziemlich schnell, aber nicht auf dem Testgerät, das ich verwendete (AMD Phenom 9750).

Update (2014-07-31)

Die neue Byte-basierte Antwort von @ CodesInChaos wurde hinzugefügt. Es scheint sowohl bei den Satztests als auch bei den Volltexttests die Nase vorn zu haben.

Aktualisierung (2015-08-20)

Hinzugefügt Airbreather Optimierungen und unsafe Variante dazu Antwort Repo. Wenn Sie im unsicheren Spiel spielen möchten, können Sie bei kurzen Strings und großen Texten enorme Leistungsgewinne gegenüber den früheren Top-Gewinnern erzielen.


405



Es gibt eine Klasse namens SoapHexBinär das macht genau das, was du willst.

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

210



Beim Schreiben von Krypto-Code ist es üblich, datenabhängige Verzweigungen und Tabellensuchen zu vermeiden, um sicherzustellen, dass die Laufzeit nicht von den Daten abhängt, da datenabhängiges Timing zu Seitenkanalangriffen führen kann.

Es ist auch ziemlich schnell.

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


Gib alle Hoffnung auf, ihr, die hier eintreten

Eine Erklärung für das seltsame bisschen Tüfteln:

  1. bytes[i] >> 4 extrahiert das High-Nibble eines Bytes
    bytes[i] & 0xF extrahiert das Low-Nibble eines Bytes
  2. b - 10
    ist < 0 für Werte b < 10, die eine Dezimalziffer wird
    ist >= 0 für Werte b > 10, aus dem ein Brief wird A zu F.
  3. Verwenden i >> 31 auf einer vorzeichenbehafteten 32-Bit-Ganzzahl extrahiert das Zeichen, dank der Zeichenerweiterung. Es wird sein -1 zum i < 0 und 0 zum i >= 0.
  4. Die Kombination von 2) und 3) zeigt das (b-10)>>31 wird sein 0 für Briefe und -1 für Ziffern.
  5. Betrachtet man den Fall für Buchstaben, wird der letzte Summand 0, und b liegt im Bereich von 10 bis 15. Wir wollen es zuordnen A(65) zu F(70), was bedeutet, dass 55'A'-10).
  6. Wenn wir den Fall für Ziffern betrachten, wollen wir den letzten Summanden so anpassen, dass er mappt b aus dem Bereich von 0 bis 9 in den Bereich 0(48) zu 9(57). Dies bedeutet, dass es -7 werden muss ('0' - 55).
    Jetzt könnten wir einfach mit 7 multiplizieren. Aber da -1 durch alle Bits dargestellt wird, die 1 sind, können wir stattdessen verwenden & -7 schon seit (0 & -7) == 0 und (-1 & -7) == -7.

Einige weitere Überlegungen:

  • Ich habe keine zweite Loop-Variable verwendet, um in den Index zu gelangen c, da die Messung zeigt, dass es aus berechnet i ist billiger.
  • Genau verwenden i < bytes.Length Als obere Grenze der Schleife erlaubt es dem JITter, Begrenzungsüberprüfungen zu eliminieren bytes[i]Also habe ich diese Variante gewählt.
  • Herstellung b Ein int erlaubt unnötige Konvertierungen von und nach Byte.

123



Wenn Sie mehr Flexibilität als wollen BitConverter, aber diese klobigen expliziten Schleifen im Stil der 1990er Jahre nicht wollen, dann können Sie Folgendes tun:

String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));

Oder wenn Sie .NET 4.0 verwenden:

String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));

(Letzteres aus einem Kommentar zum ursprünglichen Beitrag.)


83



Sie können die BitConverter.ToString-Methode verwenden:

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

Ausgabe:

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

Mehr Informationen: BitConverter.ToString Methode (Byte [])


56



Ein anderer Lookup-Tabellen-basierter Ansatz. Dieser verwendet nur eine Nachschlagetabelle für jedes Byte anstelle einer Nachschlagetabelle pro Nibble.

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

Ich habe auch Varianten davon getestet ushort, struct{char X1, X2}, struct{byte X1, X2} in der Nachschlagetabelle.

Abhängig vom Kompilierungsziel (x86, X64) hatten diese entweder die ungefähr gleiche Leistung oder waren etwas langsamer als diese Variante.


Und für noch mehr Leistung unsafe Geschwister:

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

Oder wenn Sie es für akzeptabel halten, direkt in die Zeichenfolge zu schreiben:

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

53



Ich habe heute genau das gleiche Problem gefunden und bin auf diesen Code gestoßen:

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

Quelle: Forenbeitrag byte [] Array zu Hex String (siehe den Beitrag von PZahra). Ich habe den Code ein wenig modifiziert, um das Präfix 0x zu entfernen.

Ich habe einige Leistungstests für den Code durchgeführt, und er war fast acht Mal schneller als BitConverter.ToString () (der schnellste nach Patridges Beitrag).


52



Dieses Problem könnte auch unter Verwendung einer Nachschlagetabelle gelöst werden. Dies würde sowohl für den Codierer als auch für den Decodierer eine geringe Menge an statischem Speicher erfordern. Diese Methode wird jedoch schnell sein:

  • Encoder Tabelle 512 Bytes oder 1024 Bytes (zweimal die Größe, wenn Groß- und Kleinschreibung wird gebraucht)
  • Decoder-Tabelle 256 Byte oder 64 KiB (entweder ein einzelnes Char-Look-Up oder Dual-Char-Look-Up)

Meine Lösung verwendet 1024 Byte für die Codierungstabelle und 256 Byte für die Decodierung.

Decodierung

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

Codierung

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

Vergleich

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

* diese Lösung

Hinweis

Während der Dekodierung können IOException und IndexOutOfRangeException auftreten (wenn ein Zeichen einen zu hohen Wert> 256 hat). Methoden zum De / Encoding von Streams oder Arrays sollten implementiert werden, dies ist nur ein Beweis für das Konzept.


15



Dies ist eine Antwort auf Revision 4 von Tomalaks sehr beliebte Antwort (und nachfolgende Änderungen).

Ich mache den Fall, dass diese Änderung falsch ist, und erkläre, warum sie rückgängig gemacht werden könnte. Auf dem Weg dahin könnten Sie ein oder zwei Dinge über einige Interna lernen und ein weiteres Beispiel dafür sehen, was eine vorzeitige Optimierung wirklich ist und wie sie Sie beißen kann.

tl; dr: Benutz einfach Convert.ToByte und String.Substring Wenn Sie es eilig haben ("Originalcode" unten), ist es die beste Kombination, wenn Sie nicht neu implementieren möchten Convert.ToByte. Verwenden Sie etwas fortgeschritteneres (siehe andere Antworten), das nicht verwendet wird Convert.ToByte wenn du brauchen Performance. Machen nicht benutze alles andere als String.Substring in Kombination mit Convert.ToByte, außer jemand hat etwas Interessantes in den Kommentaren zu dieser Antwort zu sagen.

Warnung: Diese Antwort kann veraltet sein ob ein Convert.ToByte(char[], Int32) Überlastung wird im Framework implementiert. Dies wird wahrscheinlich nicht bald passieren.

In der Regel sage ich nicht gerne "nicht zu früh optimieren", denn niemand weiß, wann "verfrüht" ist. Das einzige, was Sie berücksichtigen müssen, wenn Sie entscheiden, ob Sie optimieren wollen oder nicht, ist: "Habe ich Zeit und Ressourcen, um Optimierungsansätze richtig zu untersuchen?". Wenn Sie es nicht tun, dann ist es zu früh, warten Sie, bis Ihr Projekt ausgereifter ist oder bis Sie die Leistung benötigen (wenn es ein echtes Bedürfnis gibt, dann werden Sie es tun) machen die Zeit). In der Zwischenzeit tun Sie die einfachste Sache, die möglicherweise stattdessen arbeiten könnte.

Ursprünglicher Code:

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

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

Die Überarbeitung vermeidet String.Substring und benutzt a StringReader stattdessen. Der angegebene Grund ist:

Bearbeiten: Sie können die Leistung für lange Strings verbessern, indem Sie einen einzelnen String verwenden   Pass-Parser, so:

Naja, schau dir das an Referenzcode für String.Substringes ist schon klar "single-pass"; und warum sollte es nicht sein? Es arbeitet auf Byte-Ebene, nicht auf Ersatzpaaren.

Es wird jedoch eine neue Zeichenfolge zugewiesen, aber dann müssen Sie eine zuweisen, um zu übergeben Convert.ToByte sowieso. Darüber hinaus weist die in der Revision bereitgestellte Lösung bei jeder Iteration ein weiteres Objekt zu (das Zwei-Zeichen-Array). Sie können diese Zuweisung sicher außerhalb der Schleife platzieren und das Array wiederverwenden, um dies zu vermeiden.

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

Jedes Hexadezimal numeral repräsentiert ein einzelnes Oktett mit zwei Ziffern (Symbolen).

Aber warum sollte ich anrufen? StringReader.Read zweimal? Rufen Sie einfach die zweite Überladung auf und bitten Sie sie, zwei Zeichen im Array mit zwei Zeichen gleichzeitig zu lesen. und reduzieren Sie die Anzahl der Anrufe um zwei.

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

Was Sie übrig haben, ist ein String-Reader, dessen einziger hinzugefügter "Wert" ein paralleler Index ist (internal) _pos) die du selbst deklariert haben könntest ( j zum Beispiel), eine redundante Längenvariable (intern _length) und eine redundante Referenz auf die Eingabezeichenfolge (internal _s). Mit anderen Worten, es ist nutzlos.

Wenn Sie sich fragen, wie Read "liest", schau dir einfach an der Codees ruft nur an String.CopyTo auf der Eingabezeichenfolge. Der Rest ist nur der Buchhaltungsaufwand, um Werte beizubehalten, die wir nicht brauchen.

Also, entfernen Sie den String Reader bereits, und rufen Sie an CopyTo dich selber; es ist einfacher, klarer und effizienter.

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

Brauchst du wirklich eine? j Index, der in Schritten von zwei parallel zu erhöht i? Natürlich nicht, einfach multiplizieren i um zwei (was der Compiler in der Lage sein sollte, zu einem Zusatz zu optimieren).

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

Wie sieht die Lösung jetzt aus? Genau wie es am Anfang war, nur statt zu verwenden String.Substring Um die Zeichenfolge zuzuordnen und die Daten dorthin zu kopieren, verwenden Sie ein intermediäres Array, in das Sie die hexadezimalen Zahlen kopieren, und weisen Sie die Zeichenfolge dann selbst zu und kopieren Sie die Daten nochmal aus dem Array und in die Zeichenfolge (wenn Sie es im Zeichenfolgenkonstruktor übergeben). Die zweite Kopie könnte optimiert werden, wenn die Zeichenfolge bereits im internen Pool ist, aber dann String.Substring wird es auch in diesen Fällen vermeiden können.

In der Tat, wenn Sie schauen String.Substring Auch hier sehen Sie, dass es interne Kenntnisse auf niedriger Ebene verwendet, wie Strings aufgebaut sind, um den String schneller zuzuordnen, als Sie es normalerweise tun könnten, und es enthält den gleichen Code, der von verwendet wird CopyTo direkt dort, um den Anruf Overhead zu vermeiden.

String.Substring

  • Worst-Case: Eine schnelle Zuweisung, eine schnelle Kopie.
  • Bester Fall: Keine Zuweisung, keine Kopie.

Manuelle Methode

  • Worst-Case: Zwei normale Zuweisungen, eine normale Kopie, eine schnelle Kopie.
  • Bester Fall: Eine normale Zuweisung, eine normale Kopie.

Fazit? Wenn Sie verwenden möchten Convert.ToByte(String, Int32) (Da Sie diese Funktionalität nicht selbst implementieren wollen), scheint es keinen Weg zu geben, den es zu schlagen gilt String.Substring; Alles, was Sie tun, ist im Kreis zu laufen und das Rad neu zu erfinden (nur mit suboptimalen Materialien).

Beachten Sie, dass mit Convert.ToByte und String.Substring ist eine perfekte Wahl, wenn Sie keine extremen Leistungen benötigen. Denken Sie daran: Entscheiden Sie sich nur für eine Alternative, wenn Sie die Zeit und Ressourcen haben, um zu untersuchen, wie es richtig funktioniert.

Wenn es ein Convert.ToByte(char[], Int32)Natürlich wären die Dinge anders (es wäre möglich, das zu tun, was ich oben beschrieben habe und vollständig zu vermeiden String).

Ich vermute, dass Leute, die bessere Leistung melden, "vermeiden" String.Substring"auch vermeiden Convert.ToByte(String, Int32), was du wirklich tun solltest, wenn du die Performance trotzdem brauchst. Schauen Sie sich die unzähligen anderen Antworten an, um all die verschiedenen Ansätze zu entdecken.

Haftungsausschluss: Ich habe die neueste Version des Frameworks nicht dekompiliert, um zu überprüfen, ob die Referenzquelle aktuell ist, nehme ich an.

Nun, alles klingt gut und logisch, hoffentlich sogar offensichtlich, wenn Sie es geschafft haben, so weit zu kommen. Aber ist es wahr?

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

Ja!

Requisiten für Partridge für das Bank-Framework, es ist einfach zu hacken. Die verwendete Eingabe ist der folgende SHA-1-Hash, der 5000 Mal wiederholt wird, um eine 100.000 Byte lange Zeichenfolge zu erzeugen.

209113288F93A9AB8E474EA78D899AFDBB874355

Habe Spaß! (Aber mit Moderation optimieren.)


14