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


1371

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


8
Die unten akzeptierte Antwort scheint eine schreckliche Anzahl von Zeichenfolgen in der Zeichenfolge für die Bytekonvertierung zuzuweisen. Ich frage mich, wie sich dies auf die Leistung auswirkt
Wim Coenen

9
Die SoapHexBinary-Klasse macht genau das, was Sie wollen, denke ich.
Mykroft

Es scheint mir, dass das Stellen von 2 Fragen in einem Beitrag nicht ganz Standard ist.
SandRock

Antworten:


1353

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 noch mehr Varianten, zum Beispiel 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;
}

Die Verwendung Substringist die beste Option in Kombination mit Convert.ToByte. Weitere Informationen finden Sie in dieser Antwort . Wenn Sie eine bessere Leistung benötigen, müssen Sie diese vermeiden, Convert.ToBytebevor Sie sie fallen lassen können SubString.


24
Sie verwenden SubString. Weist diese Schleife nicht eine schreckliche Menge von Zeichenfolgenobjekten zu?
Wim Coenen

30
Ehrlich gesagt - bis die Leistung dramatisch beeinträchtigt wird, würde ich dies eher ignorieren und darauf vertrauen, dass Runtime und GC sich darum kümmern.
Tomalak

87
Da ein Byte aus zwei Halbbytes besteht, muss jede Hex-Zeichenfolge, die ein Byte-Array gültig darstellt, eine gerade Zeichenanzahl haben. Eine 0 sollte nirgendwo hinzugefügt werden. Wenn Sie eine 0 hinzufügen, wird davon ausgegangen, dass ungültige Daten möglicherweise gefährlich sind. Wenn überhaupt, sollte die StringToByteArray-Methode eine FormatException auslösen, wenn die Hex-Zeichenfolge eine ungerade Anzahl von Zeichen enthält.
David Boike

7
@ 00jt Sie müssen davon ausgehen, dass F == 0F ist. Entweder ist es dasselbe wie 0F, oder die Eingabe wurde abgeschnitten, und F ist tatsächlich der Anfang von etwas, das Sie nicht erhalten haben. Es liegt an Ihrem Kontext, diese Annahmen zu treffen, aber ich glaube, eine Allzweckfunktion sollte ungerade Zeichen als ungültig ablehnen, anstatt diese Annahme für den aufrufenden Code zu treffen.
David Boike

11
@DavidBoike Die Frage hatte NICHTS mit "wie man mit möglicherweise abgeschnittenen Stream-Werten umgeht" zu tun. Es geht um einen String. String myValue = 10.ToString ("X"); myValue ist "A", nicht "0A". Lesen Sie diesen String jetzt wieder in Bytes. Hoppla, Sie haben ihn gebrochen.
30.

488

Performance-Analyse

Hinweis: Neuer Leiter ab dem 20.08.2015.

Ich habe jede der verschiedenen Konvertierungsmethoden durch einige grobe StopwatchLeistungstests, einen Lauf mit einem zufälligen Satz (n = 61, 1000 Iterationen) und einen Lauf mit einem Projekt-Gutenburg-Text (n = 1.238.957, 150 Iterationen) geführt. Hier sind die Ergebnisse, ungefähr vom schnellsten zum langsamsten. Alle Messungen erfolgen in Ticks ( 10.000 Ticks = 1 ms ) und alle relativen Noten werden mit der [langsamsten] StringBuilderImplementierung verglichen . Informationen zum verwendeten Code finden Sie unten oder im Testframework-Repo, in dem ich jetzt den Code zum Ausführen dieses Codes verwalte.

Haftungsausschluss

WARNUNG: Verlassen Sie sich bei nichts Konkretem auf diese Statistiken. Sie sind einfach ein Beispiellauf von Beispieldaten. Wenn Sie wirklich erstklassige Leistung benötigen, testen Sie diese Methoden in einer Umgebung, die für Ihre Produktionsanforderungen repräsentativ ist, mit Daten, die für Ihre Verwendung repräsentativ sind.

Ergebnisse

Nachschlagetabellen haben die Führung bei der Bytemanipulation übernommen. Grundsätzlich gibt es eine Form der Vorberechnung, was ein bestimmtes Halbbyte oder Byte in hexadezimaler Form sein wird. Wenn Sie dann die Daten durchgehen, schauen Sie einfach im nächsten Abschnitt nach, um zu sehen, um welche Hex-Zeichenfolge es sich handelt. Dieser Wert wird dann auf irgendeine Weise zur resultierenden Zeichenfolgenausgabe hinzugefügt. Lange Zeit war die Byte-Manipulation, die von einigen Entwicklern möglicherweise schwerer zu lesen war, der Ansatz mit der besten Leistung.

Am besten finden Sie immer noch einige repräsentative Daten und probieren sie in einer produktionsähnlichen Umgebung aus. Wenn Sie unterschiedliche Speicherbeschränkungen haben, bevorzugen Sie möglicherweise eine Methode mit weniger Zuordnungen gegenüber einer Methode, die schneller ist, 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 Sie können das Repo klonen und Ihre eigenen Methoden hinzufügen. Bitte senden Sie eine Pull-Anfrage, wenn Sie etwas Interessantes finden oder zur Verbesserung des verwendeten Test-Frameworks beitragen möchten.

  1. Fügen Sie die neue statische Methode ( Func<byte[], string>) zu /Tests/ConvertByteArrayToHexString/Test.cs hinzu.
  2. Fügen Sie den Namen dieser Methode zum TestCandidatesRückgabewert in derselben Klasse hinzu.
  3. Stellen Sie sicher, dass Sie die gewünschte Eingabeversion (Satz oder Text) ausführen, indem Sie die Kommentare in GenerateTestInputderselben Klasse umschalten .
  4. Drücken Sie F5und warten Sie auf die Ausgabe (ein HTML-Speicherauszug wird auch im Ordner / bin 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)

Der string.Concat Array.ConvertAllVollständigkeit halber wurde eine Variante hinzugefügt (erfordert .NET 4.0). Auf Augenhöhe mit der string.JoinVersion.

Update (05.02.2012)

Test Repo enthält weitere Varianten wie StringBuilder.Append(b.ToString("X2")). Keiner hat die Ergebnisse gestört. foreachist schneller als {IEnumerable}.Aggregatezum Beispiel, BitConvertergewinnt aber trotzdem.

Update (03.04.2012)

Mykrofts SoapHexBinaryAntwort zur Analyse hinzugefügt , die den dritten Platz einnahm.

Update (15.01.2013)

Die Byte-Manipulationsantwort von CodesInChaos wurde hinzugefügt, die den ersten Platz einnahm (mit großem Abstand bei großen Textblöcken).

Update (23.05.2013)

Nathan Moinvaziris Lookup-Antwort und die Variante aus Brian Lamberts Blog wurden hinzugefügt. Beides ziemlich schnell, aber nicht die Führung auf der von mir verwendeten Testmaschine (AMD Phenom 9750).

Update (31.07.2014)

Die neue bytebasierte Suchantwort von @ CodesInChaos wurde hinzugefügt. Es scheint sowohl bei den Satztests als auch bei den Volltexttests die Führung übernommen zu haben.

Update (20.08.2015)

Hinzugefügt airbreather die Optimierungen und unsafeVariante dieser Repo - Antwort . Wenn Sie in dem unsicheren Spiel spielen möchten, können Sie sowohl bei kurzen Saiten als auch bei großen Texten enorme Leistungssteigerungen gegenüber den vorherigen Top-Gewinnern erzielen.


Möchten Sie den Code aus Waleeds Antwort testen? Es scheint sehr schnell zu sein. stackoverflow.com/questions/311165/…
Cristian Diaconescu

5
Obwohl Sie den Code zur Verfügung gestellt haben, um genau das zu tun, was Sie selbst angefordert haben, habe ich den Testcode so aktualisiert, dass er die Antwort von Waleed enthält. Abgesehen von allem Grummeln ist es viel schneller.
Patridge

2
@CodesInChaos Fertig. Und es hat in meinen Tests auch einiges gewonnen. Ich gebe noch nicht vor, eine der Top-Methoden vollständig zu verstehen, aber sie sind leicht vor direkter Interaktion zu verbergen.
Patridge

6
Diese Antwort hat nicht die Absicht, die Frage zu beantworten, was "natürlich" oder alltäglich ist. Das Ziel ist es, den Menschen einige grundlegende Leistungsmaßstäbe zu geben, da Sie diese Konvertierung häufig durchführen, wenn Sie sie durchführen müssen. Wenn jemand rohe Geschwindigkeit benötigt, führt er die Benchmarks einfach mit geeigneten Testdaten in der gewünschten Computerumgebung aus. Stecken Sie diese Methode dann in eine Erweiterungsmethode, in der Sie die Implementierung nie wieder sehen (z bytes.ToHexStringAtLudicrousSpeed(). B. ).
Patridge

2
Ich habe gerade eine auf Hochleistungstabellen basierende Implementierung erstellt. Die sichere Variante ist ungefähr 30% schneller als der aktuelle Marktführer auf meiner CPU. Die unsicheren Varianten sind noch schneller. stackoverflow.com/a/24343727/445517
CodesInChaos

244

Es gibt eine Klasse namens SoapHexBinary , die genau das tut, was Sie wollen.

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

35
SoapHexBinary ist ab .NET 1.0 verfügbar und befindet sich in mscorlib. Trotz seines lustigen Namespace macht es genau das, was die Frage gestellt hat.
Sly Gryphon

4
Toller Fund! Beachten Sie, dass Sie für GetStringToBytes wie bei der anderen Lösung ungerade Zeichenfolgen mit einer führenden 0 auffüllen müssen.
Carter Medlin

Haben Sie den Implementierungsgedanken gesehen? Die akzeptierte Antwort hat meiner Meinung nach eine bessere.
Mfloryan

6
Interessant, die Mono-Implementierung hier zu sehen: github.com/mono/mono/blob/master/mcs/class/corlib/…
Jeremy

1
SoapHexBinary wird in .NET Core / .NET Standard nicht unterstützt ...
11.

141

Beim Schreiben von Kryptocode werden häufig datenabhängige Verzweigungen und Tabellensuchen vermieden, 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 ihr hier eintretet

Eine Erklärung für das seltsame Geigen:

  1. bytes[i] >> 4extrahiert das hohe Halbbyte eines Bytes
    bytes[i] & 0xFextrahiert das niedrige Halbbyte eines Bytes
  2. b - 10
    ist < 0für Werte b < 10, die zu einer Dezimalstelle werden,
    ist >= 0für Werte b > 10, die zu einem Buchstaben von Abis werden F.
  3. Durch die Verwendung i >> 31einer vorzeichenbehafteten 32-Bit-Ganzzahl wird das Vorzeichen dank der Vorzeichenerweiterung extrahiert. Es wird -1für i < 0und 0für sein i >= 0.
  4. Die Kombination von 2) und 3) zeigt, dass (b-10)>>31dies 0für Buchstaben und -1Ziffern gilt.
  5. Wenn man den Fall für Buchstaben betrachtet, wird der letzte Summand 0und bliegt im Bereich von 10 bis 15. Wir möchten ihn A(65) bis F(70) zuordnen, was das Hinzufügen von 55 ( 'A'-10) impliziert .
  6. Wenn wir uns den Fall für Ziffern ansehen, möchten wir den letzten Summanden so anpassen, dass er bvom Bereich 0 bis 9 dem Bereich 0(48) bis 9(57) zugeordnet wird. Dies bedeutet, dass es -7 ( '0' - 55) werden muss.
    Jetzt könnten wir einfach mit 7 multiplizieren. Da -1 jedoch durch alle Bits 1 dargestellt wird, können wir stattdessen & -7da (0 & -7) == 0und verwenden (-1 & -7) == -7.

Einige weitere Überlegungen:

  • Ich habe keine zweite Schleifenvariable zum Indizieren verwendet c, da die Messung zeigt, dass die Berechnung ibilliger ist.
  • Die Verwendung genau i < bytes.Lengthder oberen Grenze der Schleife ermöglicht es dem JITter, Grenzüberprüfungen zu eliminieren bytes[i], daher habe ich diese Variante gewählt.
  • Das Erstellen beines Int ermöglicht unnötige Konvertierungen von und zu Byte.

10
Und hex stringzu byte[] array?
AaA

15
+1 für das korrekte Zitieren Ihrer Quelle nach dem Aufrufen dieses Stücks schwarzer Magie. Alle begrüßen Cthulhu.
Edward

4
Was ist mit String zu Byte []?
Syaiful Nizam Yahya

9
Nett! Für diejenigen, die eine Ausgabe in Kleinbuchstaben benötigen, ändert sich der Ausdruck offensichtlich in87 + b + (((b-10)>>31)&-39)
eXavier

2
@AaA Sie sagten " byte[] array", was wörtlich ein Array von Byte-Arrays bedeutet, oder byte[][]. Ich habe mich nur lustig gemacht.
CoolOppo

97

Wenn Sie mehr Flexibilität als möchten BitConverter, aber nicht diese klobigen expliziten Schleifen im Stil der 90er Jahre möchten, 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 Originalbeitrag.)


21
Noch kürzer: String.Concat (Array.ConvertAll (Bytes, x => x.ToString ("X2"))
Nestor

14
Noch kürzer: String.Concat (bytes.Select (b => b.ToString ("X2"))) [.NET4]
Allon Guralnek

14
Beantwortet nur die halbe Frage.
Sly Gryphon

1
Warum braucht der zweite .Net 4? String.Concat befindet sich in .Net 2.0.
Polyfun

2
Diese "90er Jahre" -Schleifen sind im Allgemeinen schneller, aber so vernachlässigbar, dass sie in den meisten Kontexten keine Rolle spielen. Immer noch erwähnenswert
Austin_Anderson

69

Ein weiterer Ansatz, der auf Nachschlagetabellen basiert. 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 mit getestet ushort von , struct{char X1, X2}, struct{byte X1, X2}in der Lookup - Tabelle.

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


Und für noch höhere Leistung ist es 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;
}

Warum tauscht das Erstellen der Nachschlagetabelle in der unsicheren Version die Halbbytes des vorberechneten Bytes aus? Ich dachte, Endianness änderte nur die Reihenfolge von Entitäten, die aus mehreren Bytes bestehen.
Raif Atef

@ RaifAtef Was hier zählt, ist nicht die Reihenfolge der Knabbereien. Aber die Reihenfolge von 16-Bit-Wörtern in einer 32-Bit-Ganzzahl. Aber ich denke darüber nach, es neu zu schreiben, damit derselbe Code unabhängig von der Endianness ausgeführt werden kann.
CodesInChaos

Beim erneuten Lesen des Codes denke ich, dass Sie dies getan haben, da die Laufzeit / CPU die Bytes umdreht, wenn Sie das Zeichen * später einem Uint * zuweisen und es zuweisen (beim Generieren des Hex-Zeichens) (da Uint nicht behandelt wird) Entspricht 2 separaten 16-Bit-Zeichen, sodass Sie sie zur Kompensation vorab umdrehen. Habe ich recht ? Endianness ist verwirrend :-).
Raif Atef

4
Dies beantwortet nur die Hälfte der Frage ... Wie wäre es mit einer Hex-Zeichenfolge zu Bytes?
Narvalex

3
@CodesInChaos Ich frage mich, ob Spanjetzt anstelle von verwendet werden kann unsafe?
Konrad

64

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

Weitere Informationen: BitConverter.ToString-Methode (Byte [])


14
Beantwortet nur die halbe Frage.
Sly Gryphon

3
Wo ist der zweite Teil der Antwort?
Sawan

56

Ich bin heute auf das gleiche Problem gestoßen 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: Forum post byte [] Array zu Hex String (siehe den Beitrag von PZahra). Ich habe den Code ein wenig geändert, um das 0x-Präfix zu entfernen.

Ich habe einige Leistungstests für den Code durchgeführt und er war fast achtmal schneller als die Verwendung von BitConverter.ToString () (laut Patridges Beitrag der schnellste).


ganz zu schweigen davon, dass dies den geringsten Speicher benötigt. Es wurden keinerlei Zwischenzeichenfolgen erstellt.
Chochos

8
Beantwortet nur die halbe Frage.
Sly Gryphon

Dies ist großartig, da es grundsätzlich auf jeder Version von NET funktioniert, einschließlich NETMF. Ein Gewinner!
Jonesome Reinstate Monica

1
Die akzeptierte Antwort enthält zwei hervorragende HexToByteArray-Methoden, die die andere Hälfte der Frage darstellen. Die Lösung von Waleed beantwortet die laufende Frage, wie dies zu tun ist, ohne dabei eine große Anzahl von Zeichenfolgen zu erstellen.
Brendten Eickstaedt

Kopiert eine neue Zeichenfolge (c) und weist sie neu zu, oder ist sie intelligent genug, um zu wissen, wann sie das Zeichen [] einfach umbrechen kann?
jjxtra

19

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

Ich mache den Fall, dass diese Bearbeitung falsch ist, und erkläre, warum sie zurückgesetzt werden könnte. Unterwegs lernen Sie vielleicht ein oder zwei Dinge über einige Interna und sehen ein weiteres Beispiel dafür, was vorzeitige Optimierung wirklich ist und wie sie Sie beißen kann.

tl; dr: Verwenden Sie einfach Convert.ToByteund String.Substringwenn Sie es eilig haben ("Originalcode" unten), ist es die beste Kombination, wenn Sie nicht erneut implementieren möchten Convert.ToByte. Verwenden Sie etwas Fortgeschritteneres (siehe andere Antworten), das nicht verwendet wird, Convert.ToBytewenn Sie Leistung benötigen . Sie nicht verwenden , etwas anderes als String.Substringin Kombination mitConvert.ToByte , es sei denn , jemand etwas interessantes dieses in den Kommentaren dieser Antwort zu sagen hat.

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

In der Regel sage ich nicht gerne "Nicht vorzeitig optimieren", weil niemand weiß, wann "vorzeitig" ist. Das einzige, was Sie berücksichtigen müssen, wenn Sie entscheiden, ob Sie optimieren möchten oder nicht, ist: "Habe ich die Zeit und die Ressourcen, um Optimierungsansätze richtig zu untersuchen?". Wenn Sie das nicht tun, dann ist es zu früh, warten Sie, bis Ihr Projekt wird reifen , oder bis Sie die Leistung benötigen (wenn es einen echten Bedarf ist, dann werden Sie machen die Zeit). Machen Sie in der Zwischenzeit das Einfachste, was stattdessen funktionieren könnte.

Originalcode:

    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 Revision vermeidet String.Substringund verwendet aStringReader stattdessen ein. Der angegebene Grund ist:

Bearbeiten: Sie können die Leistung für lange Zeichenfolgen verbessern, indem Sie einen Single-Pass-Parser verwenden, wie folgt:

Schauen Sie sich den Referenzcode für anString.Substring , ist es eindeutig schon "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 Zeichenfolge zuweisen, an die Sie Convert.ToBytetrotzdem übergeben möchten. Darüber hinaus weist die in der Revision bereitgestellte Lösung jeder Iteration ein weiteres Objekt zu (das Zwei-Zeichen-Array). Sie können diese Zuordnung 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;
    }

Jede Hexadezimalzahl numeralrepräsentiert ein einzelnes Oktett mit zwei Ziffern (Symbolen).

Aber warum dann StringReader.Readzweimal anrufen ? 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 Ihnen bleibt, ist ein String-Reader, dessen einziger zusätzlicher "Wert" ein paralleler Index (intern _pos) ist, den Sie selbst hätten deklarieren können (wie jzum Beispiel), eine redundante Längenvariable (intern _length) und eine redundante Referenz auf die Eingabe Zeichenfolge (intern_s ). Mit anderen Worten, es ist nutzlos.

Wenn Sie sich fragen, wie Read"liest", schauen Sie sich einfach den Code an . Alles, was er tut, ist ein AufrufString.CopyTo der Eingabezeichenfolge. Der Rest ist nur Buchhaltungsaufwand, um Werte zu erhalten, die wir nicht benötigen.

Entfernen Sie also den String-Reader bereits und rufen Sie sich CopyToselbst an. 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;
    }

Benötigen Sie wirklich einen jIndex, der in Schritten von zwei parallel zu erhöht wird i? Natürlich nicht, multiplizieren Sie einfach imit zwei (was der Compiler in der Lage sein sollte, eine Addition 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 anstelle der Verwendung String.Substringder Zeichenfolge und kopieren Sie die Daten , um es zu verteilen, sind Sie ein Zwischen Array , zu dem Sie die hexadezimalen Ziffern zu kopieren, dann verteilen Sie die Zeichenfolge selbst und kopieren Sie die Daten wieder aus das Array und in die Zeichenfolge (wenn Sie es im Zeichenfolgenkonstruktor übergeben). Die zweite Kopie wird möglicherweise optimiert, wenn sich die Zeichenfolge bereits im internen Pool befindet, kann sie dann String.Substringaber auch in diesen Fällen vermeiden.

Wenn Sie sich das noch String.Substringeinmal ansehen , sehen Sie, dass es einige interne Kenntnisse auf niedriger Ebene darüber verwendet, wie Zeichenfolgen erstellt werden, um die Zeichenfolge schneller zuzuweisen, als Sie es normalerweise tun könnten, und dass es denselben Code enthält, der CopyTodirekt dort verwendet wird, um dies zu vermeiden der Anruf Overhead.

String.Substring

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

Manuelle Methode

  • Worst-Case: Zwei normale Zuordnungen, eine normale Kopie, eine schnelle Kopie.
  • Bester Fall: Eine normale Zuordnung, eine normale Kopie.

Fazit? Wenn Sie verwenden möchtenConvert.ToByte(String, Int32) (weil Sie diese Funktionalität nicht selbst erneut implementieren möchten), scheint es keinen Weg zu geben, sie zu schlagenString.Substring . Alles, was Sie tun, ist im Kreis zu laufen und das Rad neu zu erfinden (nur mit nicht optimalen Materialien).

Beachten Sie, dass mit Convert.ToByteundString.Substring eine absolut gültige Wahl ist, wenn Sie keine extreme Leistung benötigen. Denken Sie daran: Entscheiden Sie sich nur für eine Alternative, wenn Sie Zeit und Ressourcen haben, um zu untersuchen, wie sie ordnungsgemäß funktioniert.

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

Ich vermute, dass Leute, die eine bessere Leistung durch "Vermeiden String.Substring" melden, auch vermeiden Convert.ToByte(String, Int32), was Sie wirklich tun sollten, wenn Sie die Leistung trotzdem brauchen. Schauen Sie sich die unzähligen anderen Antworten an, um die verschiedenen Ansätze dafür zu entdecken.

Haftungsausschluss: Ich habe die neueste Version des Frameworks nicht dekompiliert, um zu überprüfen, ob die Referenzquelle auf dem neuesten Stand ist.

Jetzt klingt alles 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 an Partridge für das Bench 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 erstellen.

209113288F93A9AB8E474EA78D899AFDBB874355

Habe Spaß! (Aber mit Mäßigung optimieren.)


Fehler: {"Konnte keine erkennbaren Ziffern finden."}
Priya Jagtap

17

Ergänzung zur Antwort durch @CodesInChaos (umgekehrte Methode)

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

Erläuterung:

& 0x0f soll auch Kleinbuchstaben unterstützen

hi = hi + 10 + ((hi >> 31) & 7); ist das gleiche wie:

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

Für '0' .. '9' ist es dasselbe wie hi = ch - 65 + 10 + 7;das ist hi = ch - 48(das liegt an 0xffffffff & 7).

Für 'A' .. 'F' ist es hi = ch - 65 + 10;(das liegt an0x00000000 & 7 ).

Für 'a' .. 'f' müssen wir große Zahlen haben, also müssen wir 32 von der Standardversion subtrahieren, indem wir einige Bits 0mit verwenden & 0x0f.

65 ist Code für 'A'

48 ist Code für '0'

7 ist die Anzahl der Buchstaben zwischen '9'und 'A'in der ASCII-Tabelle ( ...456789:;<=>?@ABCD...).


16

Dieses Problem könnte auch mithilfe einer Nachschlagetabelle gelöst werden. Dies würde eine kleine Menge statischen Speichers sowohl für den Codierer als auch für den Decodierer erfordern. Diese Methode wird jedoch schnell sein:

  • Encodertabelle 512 Bytes oder 1024 Bytes (doppelt so groß, wenn sowohl Groß- als auch Kleinbuchstaben benötigt werden)
  • Decodertabelle 256 Bytes oder 64 KiB (entweder Single-Char-Lookup oder Dual-Char-Lookup)

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

Dekodierung

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- / Codieren von Streams oder Arrays sollten implementiert werden, dies ist nur ein Proof of Concept.


2
Die Speichernutzung von 256 Bytes ist vernachlässigbar, wenn Sie Code auf der CLR ausführen.
Dolmen

9

Dies ist ein großartiger Beitrag. Ich mag Waleeds Lösung. Ich habe Patridges Test nicht durchlaufen, aber es scheint ziemlich schnell zu sein. Ich brauchte auch den umgekehrten Prozess, bei dem eine Hex-Zeichenfolge in ein Byte-Array konvertiert wurde, also schrieb ich sie als Umkehrung von Waleeds Lösung. Ich bin mir nicht sicher, ob es schneller ist als die ursprüngliche Lösung von Tomalak. Auch hier habe ich den umgekehrten Prozess nicht durch Patridges Test ausgeführt.

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

Dieser Code geht davon aus, dass die Hex-Zeichenfolge Alpha-Zeichen in Großbuchstaben verwendet, und explodiert, wenn die Hex-Zeichenfolge Alpha in Kleinbuchstaben verwendet. Möglicherweise möchten Sie aus Sicherheitsgründen eine Konvertierung in Großbuchstaben für die Eingabezeichenfolge durchführen.
Marc Novakowski

Das ist eine kluge Beobachtung, Marc. Der Code wurde geschrieben, um Waleeds Lösung umzukehren. Der ToUpper-Aufruf würde den Algorithmus etwas verlangsamen, ihm jedoch erlauben, Alpha-Zeichen in Kleinbuchstaben zu verarbeiten.
Chris F

3
Convert.ToByte (topChar + bottomChar) kann geschrieben werden als (Byte) (topChar + bottomChar)
Amir Rezaei

Um beide Fälle ohne große Leistungseinbußen zu behandeln,hexString[i] &= ~0x20;
Ben Voigt

9

Warum es komplex machen? Dies ist in Visual Studio 2008 einfach:

C #:

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

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")

2
Der Grund ist die Leistung, wenn Sie eine Hochleistungslösung benötigen. :)
Ricky

7

Um nicht auf die vielen Antworten hier einzugehen, fand ich eine ziemlich optimale (~ 4,5-mal besser als akzeptiert), unkomplizierte Implementierung des Hex-String-Parsers. Zuerst Ausgabe meiner Tests (der erste Stapel ist meine Implementierung):

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

Die Zeilen base64 und 'BitConverter'd' dienen zum Testen der Richtigkeit. Beachten Sie, dass sie gleich sind.

Die Umsetzung:

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

Ich habe einige Dinge ausprobiert unsafeund die (eindeutig redundante) Zeichen-zu-Knabber- ifSequenz auf eine andere Methode verschoben , aber dies war die schnellste, die es gab.

(Ich gebe zu, dass dies die Hälfte der Frage beantwortet. Ich hatte das Gefühl, dass die Konvertierung von Zeichenfolge-> Byte [] unterrepräsentiert war, während der Winkel von Byte [] -> Zeichenfolge gut abgedeckt zu sein scheint. Daher diese Antwort.)


1
Für die Anhänger von Knuth: Ich habe dies getan, weil ich alle paar Minuten ein paar tausend Hex-Strings analysieren muss. Deshalb ist es wichtig, dass es so schnell wie möglich ist (sozusagen in der inneren Schleife). Tomalaks Lösung ist nicht wesentlich langsamer, wenn viele solcher Parsen nicht auftreten.
Ben Mosher

5

Sichere Versionen:

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

Unsichere Versionen Für diejenigen, die Leistung bevorzugen und keine Angst vor Unsicherheit haben. Etwa 35% schneller ToHex und 10% schneller FromHex.

public static class HexUnsafeHelper
{
    [System.Diagnostics.Contracts.Pure]
    public static unsafe string ToHex(this byte[] value)
    {
        if (value == null)
            throw new ArgumentNullException("value");

        const string alphabet = @"0123456789ABCDEF";

        string result = new string(' ', checked(value.Length * 2));
        fixed (char* alphabetPtr = alphabet)
        fixed (char* resultPtr = result)
        {
            char* ptr = resultPtr;
            unchecked
            {
                for (int i = 0; i < value.Length; i++)
                {
                    *ptr++ = *(alphabetPtr + (value[i] >> 4));
                    *ptr++ = *(alphabetPtr + (value[i] & 0xF));
                }
            }
        }
        return result;
    }

    [System.Diagnostics.Contracts.Pure]
    public static unsafe byte[] FromHex(this string value)
    {
        if (value == null)
            throw new ArgumentNullException("value");
        if (value.Length % 2 != 0)
            throw new ArgumentException("Hexadecimal value length must be even.", "value");

        unchecked
        {
            byte[] result = new byte[value.Length / 2];
            fixed (char* valuePtr = value)
            {
                char* valPtr = valuePtr;
                for (int i = 0; i < result.Length; i++)
                {
                    // 0(48) - 9(57) -> 0 - 9
                    // A(65) - F(70) -> 10 - 15
                    int b = *valPtr++; // High 4 bits.
                    int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
                    b = *valPtr++; // Low 4 bits.
                    val += (b - '0') + ((('9' - b) >> 31) & -7);
                    result[i] = checked((byte)val);
                }
            }
            return result;
        }
    }
}

Übrigens Für Benchmark-Tests, bei denen das Alphabet jedes Mal initialisiert wird, wenn die aufgerufene Konvertierungsfunktion falsch ist, muss das Alphabet const (für Zeichenfolge) oder statisch schreibgeschützt (für char []) sein. Dann wird die alphabetische Konvertierung von Byte [] in Zeichenfolge so schnell wie die Byte-Manipulationsversionen.

Und natürlich muss der Test in Release (mit Optimierung) kompiliert und die Debug-Option "JIT-Optimierung unterdrücken" deaktiviert sein (dasselbe gilt für "Nur meinen Code aktivieren", wenn Code debuggbar sein muss).


5

Inverse Funktion für Waleed Eissa-Code (Hex String To Byte Array):

    public static byte[] HexToBytes(this string hexString)        
    {
        byte[] b = new byte[hexString.Length / 2];            
        char c;
        for (int i = 0; i < hexString.Length / 2; i++)
        {
            c = hexString[i * 2];
            b[i] = (byte)((c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57)) << 4);
            c = hexString[i * 2 + 1];
            b[i] += (byte)(c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57));
        }

        return b;
    }

Waleed Eissa-Funktion mit Unterstützung für Kleinbuchstaben:

    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

Erweiterungsmethoden (Haftungsausschluss: vollständig ungetesteter Code, übrigens ...):

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

etc .. Verwenden Sie eine der drei Lösungen von Tomalak (wobei die letzte eine Erweiterungsmethode für eine Zeichenfolge ist).


Sie sollten den Code wahrscheinlich testen, bevor Sie ihn für eine solche Frage anbieten.
JWW

3

Von den Entwicklern von Microsoft eine nette, einfache Konvertierung:

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

Während das oben Genannte sauber und kompakt ist, werden Performance-Junkies mithilfe von Enumeratoren darüber schreien. Mit einer verbesserten Version von Tomalaks ursprünglicher Antwort können Sie Spitzenleistungen erzielen :

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

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

   return hex.ToString();   
} 

Dies ist die schnellste aller Routinen, die ich bisher hier gesehen habe. Nehmen Sie nicht einfach mein Wort dafür ... Testen Sie jede Routine auf Leistung und überprüfen Sie den CIL-Code selbst.


2
Der Iterator ist nicht das Hauptproblem dieses Codes. Sie sollten Benchmarking b.ToSting("X2").
Dolmen

2

Und zum Einfügen in eine SQL-Zeichenfolge (wenn Sie keine Befehlsparameter verwenden):

public static String ByteArrayToSQLHexString(byte[] Source)
{
    return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}

wenn Source == nulloder Source.Length == 0wir haben ein Problem, Sir!
Andrei Krasutski

2

In Bezug auf die Geschwindigkeit scheint dies hier besser zu sein als alles andere:

  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

Ich habe den von Ihnen vorgeschlagenen Code nicht erhalten, Olipro. hex[i] + hex[i+1]anscheinend zurückgegeben ein int.

Ich hatte jedoch einige Erfolge, indem ich einige Hinweise aus dem Waleeds-Code nahm und diese zusammenhämmerte. Es ist höllisch hässlich, aber es scheint zu funktionieren und funktioniert in 1/3 der Fälle im Vergleich zu den anderen gemäß meinen Tests (unter Verwendung des Patridges-Testmechanismus). Abhängig von der Eingabegröße. Das Umschalten der ?: S, um zuerst 0-9 zu trennen, würde wahrscheinlich ein etwas schnelleres Ergebnis liefern, da es mehr Zahlen als Buchstaben gibt.

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

Diese Version von ByteArrayToHexViaByteManipulation könnte schneller sein.

Aus meinen Berichten:

  • ByteArrayToHexViaByteManipulation3: 1,68 durchschnittliche Ticks (über 1000 Läufe), 17,5X
  • ByteArrayToHexViaByteManipulation2: 1,73 durchschnittliche Ticks (über 1000 Läufe), 16,9X
  • ByteArrayToHexViaByteManipulation: 2,90 durchschnittliche Ticks (über 1000 Läufe), 10,1X
  • ByteArrayToHexViaLookupAndShift: 3,22 durchschnittliche Ticks (über 1000 Läufe), 9,1X
  • ...

    static private readonly char[] hexAlphabet = new char[]
        {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    static string ByteArrayToHexViaByteManipulation3(byte[] bytes)
    {
        char[] c = new char[bytes.Length * 2];
        byte b;
        for (int i = 0; i < bytes.Length; i++)
        {
            b = ((byte)(bytes[i] >> 4));
            c[i * 2] = hexAlphabet[b];
            b = ((byte)(bytes[i] & 0xF));
            c[i * 2 + 1] = hexAlphabet[b];
        }
        return new string(c);
    }

Und ich denke, dies ist eine Optimierung:

    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

Ich werde an diesem Bit-Fiddling-Wettbewerb teilnehmen, da ich eine Antwort habe, die auch Bit-Fiddling verwendet, um Hexadezimale zu dekodieren . Beachten Sie, dass die Verwendung von Zeichenarrays möglicherweise noch schneller ist, da das Aufrufen von StringBuilderMethoden ebenfalls einige Zeit in Anspruch nimmt.

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

Aus Java-Code konvertiert.


Hmm, ich sollte das wirklich optimieren Char[]und Charintern anstelle von Ints verwenden ...
Maarten Bodewes

Für C # wird es wahrscheinlich bevorzugt, die Variablen dort zu initialisieren, wo sie verwendet werden, anstatt außerhalb der Schleife, damit der Compiler optimiert wird. Ich bekomme in beiden Fällen die gleiche Leistung.
Peteter

2

Für die Leistung würde ich mit drphrozens Lösung gehen. Eine winzige Optimierung für den Decoder könnte darin bestehen, eine Tabelle für eines der beiden Zeichen zu verwenden, um "<< 4" zu entfernen.

Offensichtlich sind die beiden Methodenaufrufe teuer. Wenn eine Art von Überprüfung entweder auf Eingabe- oder Ausgabedaten durchgeführt wird (könnte CRC, Prüfsumme oder was auch immer sein), if (b == 255)...könnte dies übersprungen werden und dadurch auch die Methode insgesamt aufrufen.

Die Verwendung von offset++und offsetanstelle von offsetund offset + 1könnte einen theoretischen Vorteil bringen, aber ich vermute, dass der Compiler dies besser handhabt als ich.

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

Dies ist mir ein Rätsel und wurde weder getestet noch bewertet.


1

Noch eine Variation für Vielfalt:

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

Nicht auf Geschwindigkeit optimiert, aber mehr LINQy als die meisten Antworten (.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

Zwei Mashups, die die beiden Knabberoperationen zu einer zusammenfassen.

Wahrscheinlich ziemlich effiziente Version:

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

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

Und umgekehrt:

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
HexStringToByteArray ("09") gibt 0x02 zurück, was schlecht ist
CoperNick

1

Eine andere Möglichkeit ist die stackallocReduzierung des GC-Speicherdrucks:

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

Hier ist mein Schuss darauf. Ich habe ein Paar Erweiterungsklassen erstellt, um Zeichenfolge und Byte zu erweitern. Beim Test mit großen Dateien ist die Leistung mit der von Byte Manipulation 2 vergleichbar.

Der folgende Code für ToHexString ist eine optimierte Implementierung des Lookup- und Shift-Algorithmus. Es ist fast identisch mit dem von Behrooz, aber es stellt sich heraus, dass a foreachzum Iterieren verwendet wird und ein Zähler schneller ist als eine explizite Indizierungfor .

Es belegt den 2. Platz hinter Byte Manipulation 2 auf meinem Computer und ist ein gut lesbarer Code. Die folgenden Testergebnisse sind ebenfalls von Interesse:

ToHexStringCharArrayWithCharArrayLookup: 41.589,69 durchschnittliche Ticks (über 1000 Läufe), 1,5-facher ToHexStringCharArrayWithStringLookup: 50.764,06 durchschnittliche Ticks (über 1000 Läufe), 1,2-facher ToHexStringStringBuilderWithCharArrayLookup: 1,2,81

Basierend auf den obigen Ergebnissen scheint es sicher zu sein, dass:

  1. Die Nachteile für die Indizierung in eine Zeichenfolge zur Durchführung der Suche im Vergleich zu einem char-Array sind im Test für große Dateien erheblich.
  2. Die Nachteile für die Verwendung eines StringBuilder mit bekannter Kapazität im Vergleich zu einem Zeichenarray mit bekannter Größe zum Erstellen des Strings sind noch bedeutender.

Hier ist der Code:

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

Nachfolgend sind die Testergebnisse aufgeführt, die ich erhalten habe, als ich meinen Code in das Testprojekt von @ patridge auf meinem Computer eingefügt habe. Ich habe auch einen Test zum Konvertieren in ein Byte-Array von hexadezimal hinzugefügt. Die Testläufe, die meinen Code ausgeführt haben, sind ByteArrayToHexViaOptimizedLookupAndShift und HexToByteArrayViaByteManipulation. Der HexToByteArrayViaConvertToByte wurde aus XXXX entnommen. Das HexToByteArrayViaSoapHexBinary stammt aus der Antwort von @ Mykroft.

Intel Pentium III Xeon Prozessor

    Cores: 4 <br/>
    Current Clock Speed: 1576 <br/>
    Max Clock Speed: 3092 <br/>

Konvertieren eines Bytearrays in eine hexadezimale Zeichenfolgendarstellung


ByteArrayToHexViaByteManipulation2: 39.366,64 durchschnittliche Ticks (über 1000 Läufe), 22,4X

ByteArrayToHexViaOptimizedLookupAndShift: 41.588,64 durchschnittliche Ticks (über 1000 Läufe), 21,2X

ByteArrayToHexViaLookup: 55.509,56 durchschnittliche Ticks (über 1000 Läufe), 15,9X

ByteArrayToHexViaByteManipulation: 65.349,12 durchschnittliche Ticks (über 1000 Läufe), 13,5-fach

ByteArrayToHexViaLookupAndShift: 86.926,87 durchschnittliche Ticks (über 1000 Läufe), 10,2X

ByteArrayToHexStringViaBitConverter: 139.353,73 durchschnittliche Ticks (über 1000 Läufe), 6,3-fach

ByteArrayToHexViaSoapHexBinary: 314.598,77 durchschnittliche Ticks (über 1000 Läufe), 2,8-fach

ByteArrayToHexStringViaStringBuilderForEachByteToString: 344.264,63 durchschnittliche Ticks (über 1000 Läufe), 2,6-fach

ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382.623,44 durchschnittliche Ticks (über 1000 Läufe), 2,3-fach

ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818.111,95 durchschnittliche Ticks (über 1000 Läufe), 1,1-fach

ByteArrayToHexStringViaStringConcatArrayConvertAll: 839.244,84 durchschnittliche Ticks (über 1000 Läufe), 1,1-fach

ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867.303,98 durchschnittliche Ticks (über 1000 Läufe), 1,0-fach

ByteArrayToHexStringViaStringJoinArrayConvertAll: 882.710,28 durchschnittliche Ticks (über 1000 Läufe), 1,0-fach



1

Eine weitere schnelle Funktion ...

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;
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.