Wie können Sie ein Byte-Array in eine hexadezimale Zeichenfolge konvertieren und umgekehrt?
Wie können Sie ein Byte-Array in eine hexadezimale Zeichenfolge konvertieren und umgekehrt?
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 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 Substring
ist 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.ToByte
bevor Sie sie fallen lassen können SubString
.
Hinweis: Neuer Leiter ab dem 20.08.2015.
Ich habe jede der verschiedenen Konvertierungsmethoden durch einige grobe Stopwatch
Leistungstests, 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] StringBuilder
Implementierung verglichen . Informationen zum verwendeten Code finden Sie unten oder im Testframework-Repo, in dem ich jetzt den Code zum Ausführen dieses Codes verwalte.
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.
unsafe
Suche (über CodesInChaos) (hinzugefügt, um das Repo per Luftatmer zu testen )
BitConverter
(über Tomalak)
{SoapHexBinary}.ToString
(über Mykroft)
{byte}.ToString("X2")
(mit foreach
) (abgeleitet von Will Deans Antwort)
{byte}.ToString("X2")
(Verwenden von {IEnumerable}.Aggregate
, erfordert System.Linq) (über Mark)
Array.ConvertAll
(mit string.Join
) (über Will Dean)
Array.ConvertAll
(Verwendung string.Concat
von .NET 4.0 erforderlich) (über Will Dean)
{StringBuilder}.AppendFormat
(mit foreach
) (via Tomalak)
{StringBuilder}.AppendFormat
(Verwenden von {IEnumerable}.Aggregate
, erfordert System.Linq) (abgeleitet von Tomalaks Antwort)
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.
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.
Func<byte[], string>
) zu /Tests/ConvertByteArrayToHexString/Test.cs hinzu.TestCandidates
Rückgabewert in derselben Klasse hinzu.GenerateTestInput
derselben Klasse umschalten .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();
}
Waleeds Antwort zur Analyse hinzugefügt. Ziemlich schnell.
Der string.Concat
Array.ConvertAll
Vollständigkeit halber wurde eine Variante hinzugefügt (erfordert .NET 4.0). Auf Augenhöhe mit der string.Join
Version.
Test Repo enthält weitere Varianten wie StringBuilder.Append(b.ToString("X2"))
. Keiner hat die Ergebnisse gestört. foreach
ist schneller als {IEnumerable}.Aggregate
zum Beispiel, BitConverter
gewinnt aber trotzdem.
Mykrofts SoapHexBinary
Antwort zur Analyse hinzugefügt , die den dritten Platz einnahm.
Die Byte-Manipulationsantwort von CodesInChaos wurde hinzugefügt, die den ersten Platz einnahm (mit großem Abstand bei großen Textblöcken).
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).
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.
Hinzugefügt airbreather die Optimierungen und unsafe
Variante 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.
bytes.ToHexStringAtLudicrousSpeed()
. B. ).
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();
}
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:
bytes[i] >> 4
extrahiert das hohe Halbbyte eines Bytes bytes[i] & 0xF
extrahiert das niedrige Halbbyte eines Bytesb - 10
< 0
für Werte b < 10
, die zu einer Dezimalstelle werden, >= 0
für Werte b > 10
, die zu einem Buchstaben von A
bis werden F
.i >> 31
einer vorzeichenbehafteten 32-Bit-Ganzzahl wird das Vorzeichen dank der Vorzeichenerweiterung extrahiert. Es wird -1
für i < 0
und 0
für sein i >= 0
.(b-10)>>31
dies 0
für Buchstaben und -1
Ziffern gilt.0
und b
liegt 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 .b
vom Bereich 0 bis 9 dem Bereich 0
(48) bis 9
(57) zugeordnet wird. Dies bedeutet, dass es -7 ( '0' - 55
) werden muss. & -7
da (0 & -7) == 0
und verwenden (-1 & -7) == -7
.Einige weitere Überlegungen:
c
, da die Messung zeigt, dass die Berechnung i
billiger ist.i < bytes.Length
der oberen Grenze der Schleife ermöglicht es dem JITter, Grenzüberprüfungen zu eliminieren bytes[i]
, daher habe ich diese Variante gewählt.b
eines Int ermöglicht unnötige Konvertierungen von und zu Byte.hex string
zu byte[] array
?
87 + b + (((b-10)>>31)&-39)
byte[] array
", was wörtlich ein Array von Byte-Arrays bedeutet, oder byte[][]
. Ich habe mich nur lustig gemacht.
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.)
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;
}
Span
jetzt anstelle von verwendet werden kann unsafe
?
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 [])
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).
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.ToByte
und String.Substring
wenn 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.ToByte
wenn Sie Leistung benötigen . Sie nicht verwenden , etwas anderes als String.Substring
in 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.Substring
und 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.ToByte
trotzdem ü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 numeral
repräsentiert ein einzelnes Oktett mit zwei Ziffern (Symbolen).
Aber warum dann StringReader.Read
zweimal 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 j
zum 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 CopyTo
selbst 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 j
Index, der in Schritten von zwei parallel zu erhöht wird i
? Natürlich nicht, multiplizieren Sie einfach i
mit 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.Substring
der 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.Substring
aber auch in diesen Fällen vermeiden.
Wenn Sie sich das noch String.Substring
einmal 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 CopyTo
direkt dort verwendet wird, um dies zu vermeiden der Anruf Overhead.
String.Substring
Manuelle Methode
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.ToByte
undString.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.)
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 0
mit 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...
).
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:
Meine Lösung verwendet 1024 Bytes für die Codierungstabelle und 256 Bytes für die 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]));
}
private static readonly char[][] LookupTableUpper;
private static readonly char[][] LookupTableLower;
static Hex()
{
LookupTableLower = new char[256][];
LookupTableUpper = new char[256][];
for (var i = 0; i < 256; i++)
{
LookupTableLower[i] = i.ToString("x2").ToCharArray();
LookupTableUpper[i] = i.ToString("X2").ToCharArray();
}
}
public static char[] ToCharLower(byte[] b, int bOffset)
{
return LookupTableLower[b[bOffset]];
}
public static char[] ToCharUpper(byte[] b, int bOffset)
{
return LookupTableUpper[b[bOffset]];
}
StringBuilderToStringFromBytes: 106148
BitConverterToStringFromBytes: 15783
ArrayConvertAllToStringFromBytes: 54290
ByteManipulationToCharArray: 8444
TableBasedToCharArray: 5651 *
* diese Lösung
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.
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;
}
hexString[i] &= ~0x20;
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("-", "")
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 unsafe
und die (eindeutig redundante) Zeichen-zu-Knabber- if
Sequenz 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.)
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).
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);
}
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).
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.
b.ToSting("X2")
.
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("-", "");
}
Source == null
oder Source.Length == 0
wir haben ein Problem, Sir!
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);
}
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;
}
Diese Version von ByteArrayToHexViaByteManipulation könnte schneller sein.
Aus meinen Berichten:
...
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);
}
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 StringBuilder
Methoden 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.
Char[]
und Char
intern anstelle von Ints verwenden ...
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 offset
anstelle von offset
und offset + 1
kö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.
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;
}
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
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;
}
Eine andere Möglichkeit ist die stackalloc
Reduzierung 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);
}
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 foreach
zum 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:
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
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;
}