Come si può convertire un array di byte in una stringa esadecimale e viceversa?
Come si può convertire un array di byte in una stringa esadecimale e viceversa?
Risposte:
O:
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();
}
o:
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
Ci sono anche più varianti di farlo, ad esempio qui .
La conversione inversa andrebbe così:
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;
}
L'uso Substring
è l'opzione migliore in combinazione con Convert.ToByte
. Vedi questa risposta per maggiori informazioni. Se hai bisogno di prestazioni migliori, devi evitarlo Convert.ToByte
prima di poterlo abbandonare SubString
.
Nota: nuovo leader dal 20-08-2015.
Ho eseguito ciascuno dei vari metodi di conversione attraverso alcuni Stopwatch
test di prestazione grezzi , una corsa con una frase casuale (n = 61, 1000 iterazioni) e una corsa con un testo di Project Gutenburg (n = 1.238.957, 150 iterazioni). Ecco i risultati, approssimativamente dal più veloce al più lento. Tutte le misurazioni sono in tick ( 10.000 tick = 1 ms ) e tutte le note relative vengono confrontate con l' StringBuilder
implementazione [più lenta] . Per il codice utilizzato, vedere di seguito o il repository del framework di test in cui ora mantengo il codice per l'esecuzione.
ATTENZIONE: non fare affidamento su queste statistiche per qualcosa di concreto; sono semplicemente una serie di dati campione. Se hai davvero bisogno di prestazioni di altissimo livello, prova questi metodi in un ambiente rappresentativo delle tue esigenze di produzione con i dati rappresentativi di ciò che utilizzerai.
unsafe
(tramite CodesInChaos) (aggiunta al repository di prova tramite airbreather )
BitConverter
(via Tomalak)
{SoapHexBinary}.ToString
(tramite Mykroft)
{byte}.ToString("X2")
(usando foreach
) (derivato dalla risposta di Will Dean)
{byte}.ToString("X2")
(utilizzando {IEnumerable}.Aggregate
, richiede System.Linq) (tramite Mark)
Array.ConvertAll
(utilizzando string.Join
) (tramite Will Dean)
Array.ConvertAll
(utilizzando string.Concat
, richiede .NET 4.0) (tramite Will Dean)
{StringBuilder}.AppendFormat
(utilizzando foreach
) (tramite Tomalak)
{StringBuilder}.AppendFormat
(usando {IEnumerable}.Aggregate
, richiede System.Linq) (derivato dalla risposta di Tomalak)
Le tabelle di ricerca hanno preso il comando della manipolazione dei byte. Fondamentalmente, esiste una qualche forma di pre-calcolo di ciò che qualsiasi dato bocconcino o byte sarà in esadecimale. Quindi, mentre si sfogliano i dati, è sufficiente cercare la parte successiva per vedere quale stringa esadecimale sarebbe. Tale valore viene quindi aggiunto all'output della stringa risultante in qualche modo. Per molto tempo la manipolazione dei byte, potenzialmente più difficile da leggere per alcuni sviluppatori, è stata l'approccio più performante.
La tua scommessa migliore sarà ancora trovare alcuni dati rappresentativi e provarli in un ambiente simile alla produzione. Se hai diversi vincoli di memoria, potresti preferire un metodo con meno allocazioni a uno che sarebbe più veloce ma consuma più memoria.
Sentiti libero di giocare con il codice di test che ho usato. Una versione è inclusa qui, ma sentiti libero di clonare il repository e aggiungere i tuoi metodi. Invia una richiesta pull se trovi qualcosa di interessante o desideri contribuire a migliorare il framework di test che utilizza.
Func<byte[], string>
) a /Tests/ConvertByteArrayToHexString/Test.cs.TestCandidates
valore restituito nella stessa classe.GenerateTestInput
in quella stessa classe.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();
}
Aggiunta la risposta di Waleed all'analisi. Abbastanza veloce.
Aggiunta string.Concat
Array.ConvertAll
variante per completezza (richiede .NET 4.0). Alla pari constring.Join
versione.
Il repository di prova include più varianti come StringBuilder.Append(b.ToString("X2"))
. Nessuno ha stravolto i risultati. foreach
è più veloce di {IEnumerable}.Aggregate
, per esempio, ma BitConverter
vince ancora.
Aggiunto Mykroft SoapHexBinary
risposta all'analisi, che ha preso il terzo posto.
Aggiunta la risposta alla manipolazione dei byte di CodesInChaos, che ha preso il primo posto (con un ampio margine su grandi blocchi di testo).
Aggiunta la risposta di ricerca di Nathan Moinvaziri e la variante dal blog di Brian Lambert. Entrambi piuttosto veloci, ma non prendendo il comando sulla macchina di prova che ho usato (AMD Phenom 9750).
Aggiunta la nuova risposta di ricerca basata su byte di @ CodesInChaos. Sembra aver preso il comando sia per i test delle frasi che per i test full-text.
Aggiunte ottimizzazioni e varianti di airbreather al repository diunsafe
questa risposta . Se vuoi giocare in un gioco non sicuro, puoi ottenere enormi guadagni in termini di prestazioni rispetto a tutti i precedenti vincitori su stringhe brevi e testi di grandi dimensioni.
bytes.ToHexStringAtLudicrousSpeed()
.).
C'è una classe chiamata SoapHexBinary che fa esattamente quello che vuoi.
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();
}
Quando si scrive codice crittografico è comune evitare rami dipendenti da dati e ricerche di tabelle per garantire che il runtime non dipenda dai dati, poiché il tempismo dipendente dai dati può portare a attacchi sui canali laterali.
È anche abbastanza veloce.
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
Abbandona ogni speranza, voi che entrate qui
Una spiegazione dello strano bit che armeggia:
bytes[i] >> 4
estrae il nibble alto di un byte bytes[i] & 0xF
estrae il nibble basso di un byteb - 10
< 0
per i valori b < 10
, che diventeranno una cifra decimale >= 0
per i valori b > 10
, che diventeranno una lettera da A
a F
.i >> 31
su un intero con segno a 32 bit estrae il segno, grazie all'estensione del segno. Sarà -1
per i < 0
e 0
per i >= 0
.(b-10)>>31
sarà 0
per lettere e -1
cifre.0
ed b
è compreso tra 10 e 15. Vogliamo mapparlo su A
(65) a F
(70), il che implica l'aggiunta di 55 ( 'A'-10
).b
dall'intervallo da 0 a 9 all'intervallo da 0
(48) a 9
(57). Ciò significa che deve diventare -7 ( '0' - 55
). & -7
da (0 & -7) == 0
e (-1 & -7) == -7
.Alcune ulteriori considerazioni:
c
, poiché la misurazione mostra che calcolarla dai
è più economico.i < bytes.Length
come limite superiore del loop consente a JITter di eliminare i controlli dei limitibytes[i]
, quindi ho scelto quella variante.b
un int permette conversioni non necessarie da e verso byte.hex string
a byte[] array
?
87 + b + (((b-10)>>31)&-39)
byte[] array
", che significa letteralmente una matrice di array di byte, oppure byte[][]
. Mi stavo solo prendendo in giro.
Se vuoi più flessibilità di BitConverter
, ma non vuoi quei goffi loop espliciti in stile anni '90, allora puoi fare:
String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));
Oppure, se si utilizza .NET 4.0:
String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));
(Quest'ultimo da un commento sul post originale.)
Un altro approccio basato sulla tabella di ricerca. Questo utilizza solo una tabella di ricerca per ogni byte, anziché una tabella di ricerca per 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);
}
Ho anche provato varianti di questo utilizzo ushort
, struct{char X1, X2}
, struct{byte X1, X2}
nella tabella di ricerca.
A seconda del target di compilazione (x86, X64) quelli avevano le stesse prestazioni o erano leggermente più lenti di questa variante.
E per prestazioni ancora più elevate, i suoi unsafe
fratelli:
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);
}
O se ritieni accettabile scrivere direttamente nella stringa:
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
può essere usato ora invece di unsafe
??
È possibile utilizzare il metodo BitConverter.ToString:
byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256}
Console.WriteLine( BitConverter.ToString(bytes));
Produzione:
00-01-02-04-08-10-20-40-80-FF
Ulteriori informazioni: Metodo BitConverter.ToString (Byte [])
Oggi ho riscontrato lo stesso problema e ho riscontrato questo codice:
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);
}
Fonte: Forum post byte [] Array to Hex String (vedi il post di PZahra). Ho modificato un po 'il codice per rimuovere il prefisso 0x.
Ho fatto alcuni test delle prestazioni del codice ed era quasi otto volte più veloce rispetto all'utilizzo di BitConverter.ToString () (il più veloce secondo il post di Patridge).
Questa è una risposta alla revisione 4 della risposta molto popolare di Tomalak (e successive modifiche).
Farò il caso che questa modifica sia sbagliata e spiegherò perché potrebbe essere ripristinata. Lungo la strada, potresti imparare qualcosa o due su alcuni interni e vedere ancora un altro esempio di cosa sia davvero l'ottimizzazione prematura e come può morderti.
tl; dr: basta usare Convert.ToByte
e String.Substring
se hai fretta ("Codice originale" di seguito), è la combinazione migliore se non vuoi implementare di nuovo Convert.ToByte
. Usa qualcosa di più avanzato (vedi altre risposte) che non usi Convert.ToByte
se hai bisogno di prestazioni. Evitare Non usare altro che String.Substring
in combinazione con Convert.ToByte
, a meno che qualcuno ha qualcosa di interessante da dire su questo nei commenti di questa risposta.
avvertenza: questa risposta può diventare obsoleta seConvert.ToByte(char[], Int32)
viene implementato un sovraccarico nel framework. È improbabile che ciò accada presto.
Come regola generale, non mi piace molto dire "non ottimizzare prematuramente", perché nessuno sa quando sia "prematuro". L'unica cosa che devi considerare quando decidi se ottimizzare o meno è: "Ho il tempo e le risorse per investigare correttamente sugli approcci di ottimizzazione?". Se non lo fai, allora è troppo presto, aspetta che il tuo progetto sia più maturo o fino a quando non hai bisogno delle prestazioni (se ce n'è davvero bisogno, allora farai il tempo). Nel frattempo, fai la cosa più semplice che potrebbe funzionare.
Codice originale:
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;
}
Revisione 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;
}
La revisione evita String.Substring
e usa StringReader
invece un . Il motivo dato è:
Modifica: è possibile migliorare le prestazioni per stringhe lunghe utilizzando un parser a passaggio singolo, in questo modo:
Bene, guardando il codice di riferimento perString.Substring
, è chiaramente già "single-pass"; e perché non dovrebbe essere? Funziona a livello di byte, non su coppie surrogate.
Alloca tuttavia una nuova stringa, ma è necessario allocare una per passare Convert.ToByte
comunque. Inoltre, la soluzione fornita nella revisione alloca ancora un altro oggetto su ogni iterazione (l'array a due caratteri); puoi tranquillamente mettere tale allocazione fuori dal ciclo e riutilizzare l'array per evitarlo.
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;
}
Ogni esadecimale numeral
rappresenta un singolo ottetto usando due cifre (simboli).
Ma allora, perché chiamare StringReader.Read
due volte? Basta chiamare il suo secondo sovraccarico e chiedergli di leggere due caratteri nell'array a due caratteri contemporaneamente; e ridurre la quantità di chiamate di due.
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;
}
Ciò che ti rimane è un lettore di stringhe il cui unico "valore" aggiunto è un indice parallelo (interno _pos
) che avresti potuto dichiarare (come j
ad esempio), una variabile di lunghezza ridondante (interna _length
) e un riferimento ridondante all'input stringa (interna _s
). In altre parole, è inutile.
Se ti chiedi come Read
"legge", basta guardare il codice , tutto ciò che fa è chiamare String.CopyTo
sulla stringa di input. Il resto è solo un sovraccarico di contabilità per mantenere valori di cui non abbiamo bisogno.
Quindi, rimuovi già il lettore di stringhe e chiamati CopyTo
; è più semplice, più chiaro e più efficiente.
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;
}
Hai davvero bisogno di un j
indice che aumenti in incrementi di due paralleli a i
? Certo che no, basta moltiplicare i
per due (che il compilatore dovrebbe essere in grado di ottimizzare per un'aggiunta).
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;
}
Come si presenta la soluzione ora? Esattamente come all'inizio, solo invece di utilizzare String.Substring
per allocare la stringa e copiarvi i dati, stai usando un array intermedio in cui copiare i numeri esadecimali, quindi allocare tu stesso la stringa e copiare nuovamente i dati da l'array e nella stringa (quando lo si passa nel costruttore della stringa). La seconda copia potrebbe essere ottimizzata se la stringa si trova già nel pool interno, ma String.Substring
in questi casi sarà anche possibile evitarlo.
In effetti, se guardi di String.Substring
nuovo, vedi che usa una conoscenza interna di basso livello su come le stringhe sono costruite per allocare la stringa più velocemente di quanto potresti normalmente fare, e incorpora lo stesso codice usato CopyTo
direttamente lì dentro per evitare il sovraccarico di chiamata.
String.Substring
Metodo manuale
Conclusione? Se vuoi usareConvert.ToByte(String, Int32)
(perché non vuoi implementare quella funzionalità da solo), non sembra esserci un modo per battere String.Substring
; tutto ciò che fai è correre in cerchio, reinventando la ruota (solo con materiali non ottimali).
Nota che usare Convert.ToByte
ed String.Substring
è una scelta perfettamente valida se non hai bisogno di prestazioni estreme. Ricorda: optare per un'alternativa solo se hai il tempo e le risorse per indagare su come funziona correttamente.
Se ci fosse un Convert.ToByte(char[], Int32)
, le cose sarebbero ovviamente diverse (sarebbe possibile fare ciò che ho descritto sopra ed evitare completamente String
).
Ho il sospetto che anche le persone che segnalano prestazioni migliori "evitandole String.Substring
" lo evitino Convert.ToByte(String, Int32)
, cosa che dovresti davvero fare se hai comunque bisogno delle prestazioni. Guarda le innumerevoli altre risposte per scoprire tutti i diversi approcci per farlo.
Dichiarazione di non responsabilità: non ho decompilato l'ultima versione del framework per verificare che l'origine di riferimento sia aggiornata, presumo che lo sia.
Ora, tutto suona bene e logico, si spera persino ovvio se sei riuscito ad arrivare così lontano. Ma è vero?
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
Sì!
Props to Partridge per il framework da banco, è facile da hackerare. L'input utilizzato è il seguente hash SHA-1 ripetuto 5000 volte per creare una stringa lunga 100.000 byte.
209113288F93A9AB8E474EA78D899AFDBB874355
Divertiti! (Ma ottimizza con moderazione.)
Complemento per la risposta di @CodesInChaos (metodo inverso)
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;
}
Spiegazione:
& 0x0f
è supportare anche lettere minuscole
hi = hi + 10 + ((hi >> 31) & 7);
equivale a:
hi = ch-65 + 10 + (((ch-65) >> 31) & 7);
Per '0' .. '9' è lo stesso di quello hi = ch - 65 + 10 + 7;
che è hi = ch - 48
(questo a causa di 0xffffffff & 7
).
Per "A" ... "F" lo è hi = ch - 65 + 10;
(a causa di 0x00000000 & 7
).
Per 'a' .. 'f' dobbiamo numeri grandi, quindi dobbiamo sottrarre 32 dalla versione predefinita facendo alcuni bit 0
usando & 0x0f
.
65 è il codice per 'A'
48 è il codice per '0'
7 è il numero di lettere tra '9'
e 'A'
nella tabella ASCII ( ...456789:;<=>?@ABCD...
).
Questo problema potrebbe anche essere risolto utilizzando una tabella di ricerca. Ciò richiederebbe una piccola quantità di memoria statica sia per l'encoder che per il decoder. Questo metodo sarà comunque veloce:
La mia soluzione utilizza 1024 byte per la tabella di codifica e 256 byte per la decodifica.
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 *
* questa soluzione
Durante la decodifica potrebbero verificarsi IOException e IndexOutOfRangeException (se un carattere ha un valore troppo alto> 256). Dovrebbero essere implementati metodi per de / codificare stream o array, questa è solo una prova di concetto.
Questo è un grande post. Mi piace la soluzione di Waleed. Non ho eseguito il test di Patridge ma sembra essere abbastanza veloce. Avevo anche bisogno del processo inverso, convertendo una stringa esadecimale in un array di byte, quindi l'ho scritto come un'inversione della soluzione di Waleed. Non sono sicuro che sia più veloce della soluzione originale di Tomalak. Ancora una volta, non ho nemmeno eseguito il processo inverso attraverso il test di Patridge.
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;
Perché renderlo complesso? Questo è semplice in Visual Studio 2008:
C #:
string hex = BitConverter.ToString(YourByteArray).Replace("-", "");
VB:
Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Non per accatastarmi con le molte risposte qui, ma ho trovato un'implementazione abbastanza ottimale (~ 4.5x meglio di quella accettata) e semplice del parser di stringhe esadecimali. Innanzitutto, output dai miei test (il primo batch è la mia implementazione):
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
Le linee base64 e 'BitConverter'd' sono lì per testare la correttezza. Si noti che sono uguali.
L'implemento:
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;
}
Ho provato alcune cose con unsafe
e spostando la if
sequenza carattere-nibble (chiaramente ridondante) in un altro metodo, ma questo è stato il più veloce che abbia ottenuto.
(Ammetto che questo risponde a metà della domanda. Ho sentito che la conversione string-> byte [] era sottorappresentata, mentre l'angolo della stringa byte [] -> sembra essere ben coperto. Pertanto, questa risposta.)
Versioni sicure:
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;
}
}
}
Versioni non sicure Per coloro che preferiscono le prestazioni e non temono l'insicurezza. Circa il 35% più veloce di ToHex e il 10% più veloce di FromHex.
public static class HexUnsafeHelper
{
[System.Diagnostics.Contracts.Pure]
public static unsafe string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string alphabet = @"0123456789ABCDEF";
string result = new string(' ', checked(value.Length * 2));
fixed (char* alphabetPtr = alphabet)
fixed (char* resultPtr = result)
{
char* ptr = resultPtr;
unchecked
{
for (int i = 0; i < value.Length; i++)
{
*ptr++ = *(alphabetPtr + (value[i] >> 4));
*ptr++ = *(alphabetPtr + (value[i] & 0xF));
}
}
}
return result;
}
[System.Diagnostics.Contracts.Pure]
public static unsafe byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
fixed (char* valuePtr = value)
{
char* valPtr = valuePtr;
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = *valPtr++; // High 4 bits.
int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
b = *valPtr++; // Low 4 bits.
val += (b - '0') + ((('9' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
}
return result;
}
}
}
BTW Per i test di benchmark che inizializzano l'alfabeto ogni volta che la funzione di conversione chiamata è errata, l'alfabeto deve essere const (per stringa) o statico di sola lettura (per char []). Quindi la conversione in ordine alfabetico di byte [] in stringa diventa più veloce delle versioni di manipolazione dei byte.
E ovviamente il test deve essere compilato in Release (con ottimizzazione) e con l'opzione di debug "Elimina ottimizzazione JIT" disattivata (lo stesso per "Abilita solo il mio codice" se il codice deve essere debuggabile).
Funzione inversa per codice Wissaed Eissa (stringa esadecimale a matrice di byte):
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;
}
Funzione Eissa Waleed con supporto per minuscole:
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);
}
Metodi di estensione (dichiarazione di non responsabilità: codice completamente non testato, BTW ...):
public static class ByteExtensions
{
public static string ToHexString(this byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
{
hex.AppendFormat("{0:x2}", b);
}
return hex.ToString();
}
}
ecc. Usa una delle tre soluzioni di Tomalak (l'ultima è un metodo di estensione su una stringa).
Dagli sviluppatori di Microsoft, una conversione piacevole e semplice:
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();
}
Mentre quanto sopra è pulito e compatto, i drogati delle prestazioni lo urleranno usando gli enumeratori. Puoi ottenere le massime prestazioni con una versione migliorata della risposta originale di Tomalak :
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();
}
Questa è la più veloce di tutte le routine che ho visto pubblicato qui finora. Non limitarti a crederci sulla parola ... prova le prestazioni di ogni routine e controlla tu stesso il suo codice CIL.
b.ToSting("X2")
.
E per l'inserimento in una stringa SQL (se non si utilizzano i parametri di comando):
public static String ByteArrayToSQLHexString(byte[] Source)
{
return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
Source == null
o Source.Length == 0
abbiamo un problema signore!
In termini di velocità, questo sembra essere meglio di qualsiasi altra cosa qui:
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);
}
Non ho ricevuto il codice che hai suggerito di funzionare, Olipro. hex[i] + hex[i+1]
apparentemente restituito un int
.
Tuttavia, ho avuto un certo successo prendendo alcuni suggerimenti dal codice Waleeds e martellandolo insieme. È brutto da morire ma sembra funzionare e si esibisce all'1 / 3 del tempo rispetto agli altri secondo i miei test (usando il meccanismo di test dei patridges). A seconda della dimensione dell'input. Cambiando prima?? S per separare prima 0-9 probabilmente si otterrebbe un risultato leggermente più veloce poiché ci sono più numeri che lettere.
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;
}
Questa versione di ByteArrayToHexViaByteManipulation potrebbe essere più veloce.
Dai miei rapporti:
...
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);
}
E penso che questo sia un'ottimizzazione:
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);
}
Parteciperò a questa competizione poco impegnativa poiché ho una risposta che utilizza anche la manipolazione dei bit per decodificare i valori esadecimali. Si noti che l'utilizzo di array di caratteri può essere ancora più veloce poiché anche i StringBuilder
metodi di chiamata richiedono tempo.
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;
}
Convertito dal codice Java.
Char[]
e usarlo Char
internamente anziché
Per le prestazioni andrei con la soluzione di drphrozens. Una piccola ottimizzazione per il decodificatore potrebbe essere quella di utilizzare una tabella per entrambi i caratteri per eliminare "<< 4".
Chiaramente le due chiamate di metodo sono costose. Se viene effettuato un tipo di controllo sui dati di input o output (potrebbe essere CRC, checksum o altro), si if (b == 255)...
potrebbe saltare e quindi anche il metodo chiama del tutto.
Usare offset++
e offset
invece di offset
e offset + 1
potrebbe dare qualche vantaggio teorico, ma sospetto che il compilatore lo gestisca meglio di me.
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]));
}
Questo è appena al di sopra della mia testa e non è stato testato o analizzato.
Ancora un'altra variazione per la diversità:
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;
}
Non ottimizzato per la velocità, ma più LINQy della maggior parte delle risposte (.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
Due mashup che piegano le due operazioni di sgranocchiatura in una.
Versione probabilmente abbastanza efficiente:
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 );
}
Decadente versione linq-con-bit-hacking:
public static string ByteArrayToString(byte[] ba)
{
return string.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) );
}
E viceversa:
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;
}
Un altro modo è utilizzare stackalloc
per ridurre la pressione della memoria GC:
static string ByteToHexBitFiddle(byte[] bytes)
{
var c = stackalloc char[bytes.Length * 2 + 1];
int b;
for (int i = 0; i < bytes.Length; ++i)
{
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
}
c[bytes.Length * 2 ] = '\0';
return new string(c);
}
Ecco il mio colpo. Ho creato un paio di classi di estensione per estendere stringa e byte. Nel test di file di grandi dimensioni, le prestazioni sono paragonabili a Byte Manipulation 2.
Il codice seguente per ToHexString è un'implementazione ottimizzata dell'algoritmo di ricerca e spostamento. È quasi identico a quello di Behrooz, ma si scopre che usa it foreach
per iterare e un contatore è più veloce di un indice esplicito for
.
Viene al 2 ° posto dietro a Byte Manipulation 2 sulla mia macchina ed è un codice molto leggibile. Anche i seguenti risultati dei test sono interessanti:
ToHexStringCharArrayWithCharArrayLookup: 41.589,69 tick medi (oltre 1000 esecuzioni), 1.5X ToHexStringCharArrayWithStringLookup: tick medi 50.764.06 (oltre 1000 esecuzioni), 1.2X ToHexStringStringStringBuilderWithCharArrayLookup in media: 1.0.8.8s (62.812)
Sulla base dei risultati precedenti sembra sicuro concludere che:
Ecco il codice:
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;
}
}
}
Di seguito sono riportati i risultati dei test che ho ottenuto quando ho inserito il mio codice nel progetto di test di @ patridge sulla mia macchina. Ho anche aggiunto un test per la conversione in una matrice di byte da esadecimale. I test eseguiti che hanno esercitato il mio codice sono ByteArrayToHexViaOptimizedLookupAndShift e HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte è stato prelevato da XXXX. HexToByteArrayViaSoapHexBinary è quello della risposta di @ Mykroft.
Processore Intel Pentium III Xeon
Cores: 4 <br/> Current Clock Speed: 1576 <br/> Max Clock Speed: 3092 <br/>
Conversione di array di byte in rappresentazione di stringhe esadecimali
ByteArrayToHexViaByteManipulation2: 39.366,64 tick medi (oltre 1000 corse), 22,4X
ByteArrayToHexViaOptimizedLookupAndShift: 41.588,64 tick medi (oltre 1000 corse), 21.2X
ByteArrayToHexViaLookup: 55.509,56 tick medi (oltre 1000 corse), 15.9X
ByteArrayToHexViaByteManipulation: 65.349,12 tick medi (oltre 1000 corse), 13,5X
ByteArrayToHexViaLookupAndShift: 86.926,87 tick medi (oltre 1000 corse), 10.2X
ByteArrayToHexStringViaBitConverter: 139.353,73 tick medi (oltre 1000 corse), 6.3X
ByteArrayToHexViaSoapHexBinary: 314.598,77 tick medi (oltre 1000 corse), 2.8X
ByteArrayToHexStringViaStringBuilderForEachByteToString: 344.264,63 tick medi (oltre 1000 corse), 2.6X
ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382.623,44 tick medi (oltre 1000 corse), 2.3X
ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818.111,95 tick medi (oltre 1000 corse), 1.1X
ByteArrayToHexStringViaStringConcatArrayConvertAll: 839.244,84 tick medi (oltre 1000 corse), 1.1X
ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867.303,98 tick medi (oltre 1000 corse), 1.0X
ByteArrayToHexStringViaStringJoinArrayConvertAll: 882.710,28 tick medi (oltre 1000 corse), 1.0X
Un'altra funzione veloce ...
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;
}