Come si converte un array di byte in una stringa esadecimale e viceversa?


1372

Come si può convertire un array di byte in una stringa esadecimale e viceversa?


8
La risposta accettata di seguito sembra allocare una quantità orribile di stringhe nella stringa alla conversione in byte. Mi chiedo come questo influisca sulle prestazioni
Wim Coenen,

9
La classe SoapHexBinary fa esattamente quello che vuoi, penso.
Mykroft

Mi sembra che porre 2 domande in 1 post non sia del tutto standard.
SandRock,

Risposte:


1353

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.ToByteprima di poterlo abbandonare SubString.


24
Stai usando SubString. Questo loop non alloca una quantità orribile di oggetti stringa?
Wim Coenen,

30
Onestamente - fino a quando non ridurrà drasticamente le prestazioni, tenderei a ignorarlo e mi fiderei del Runtime e del GC per occuparmene.
Tomalak,

87
Poiché un byte è due nibble, qualsiasi stringa esadecimale che rappresenta validamente un array di byte deve avere un conteggio dei caratteri pari. Uno 0 non dovrebbe essere aggiunto da nessuna parte: aggiungerne uno significherebbe dati non validi potenzialmente pericolosi. Semmai, il metodo StringToByteArray dovrebbe generare un FormatException se la stringa esadecimale contiene un numero dispari di caratteri.
David Boike,

7
@ 00jt Devi supporre che F == 0F. O è uguale a 0F o l'ingresso è stato troncato e F è in realtà l'inizio di qualcosa che non hai ricevuto. Spetta al tuo contesto fare queste ipotesi, ma credo che una funzione per scopi generali dovrebbe rifiutare i caratteri dispari come non validi invece di fare quell'ipotesi per il codice chiamante.
David Boike,

11
@DavidBoike La domanda non aveva nulla a che fare con "come gestire i valori di flusso eventualmente troncati" Sta parlando di una stringa. String myValue = 10.ToString ("X"); myValue è "A" non "0A". Ora vai a leggere quella stringa di nuovo in byte, oops l'hai rotta.
00jt

488

Analisi di performance

Nota: nuovo leader dal 20-08-2015.

Ho eseguito ciascuno dei vari metodi di conversione attraverso alcuni Stopwatchtest 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' StringBuilderimplementazione [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.

disconoscimento

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.

risultati

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.

Codice test

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.

  1. Aggiungi il nuovo metodo statico (Func<byte[], string> ) a /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Aggiungi il nome di quel metodo a TestCandidates valore restituito nella stessa classe.
  3. Assicurati di eseguire la versione di input desiderata, frase o testo, attivando i commenti GenerateTestInput in quella stessa classe.
  4. Premi F5e attendi l'output (nella cartella / bin viene generato anche un dump HTML).
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();
}

Aggiornamento (13/01/2010)

Aggiunta la risposta di Waleed all'analisi. Abbastanza veloce.

Aggiornamento (2011-10-05)

Aggiunta string.Concat Array.ConvertAllvariante per completezza (richiede .NET 4.0). Alla pari constring.Join versione.

Aggiornamento (05/02/2012)

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 BitConvertervince ancora.

Aggiornamento (03-04-2012)

Aggiunto Mykroft SoapHexBinary risposta all'analisi, che ha preso il terzo posto.

Aggiornamento (15-01-2013)

Aggiunta la risposta alla manipolazione dei byte di CodesInChaos, che ha preso il primo posto (con un ampio margine su grandi blocchi di testo).

Aggiornamento (23/05/2013)

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).

Aggiornamento (2014-07-31)

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.

Aggiornamento (20-08-2015)

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.


Ti andrebbe di testare il codice dalla risposta di Waleed? Sembra essere molto veloce. stackoverflow.com/questions/311165/…
Cristian Diaconescu,

5
Nonostante abbia reso il codice disponibile per te per fare esattamente quello che hai richiesto da solo, ho aggiornato il codice di test per includere la risposta di Waleed. A parte la scontrosità, è molto più veloce.
Patridge

2
@CodesInChaos Fatto. E ha vinto anche nei miei test un bel po '. Non pretendo ancora di comprendere pienamente nessuno dei migliori metodi, ma sono facilmente nascosti dall'interazione diretta.
Patridge

6
Questa risposta non ha alcuna intenzione di rispondere alla domanda su cosa sia "naturale" o banale. L'obiettivo è fornire alle persone alcuni parametri di riferimento prestazionali di base poiché, quando è necessario effettuare queste conversioni, si tende a fare molto. Se qualcuno ha bisogno di una velocità pura, esegue semplicemente i benchmark con alcuni dati di test appropriati nell'ambiente di elaborazione desiderato. Quindi, riponi quel metodo in un metodo di estensione in cui non guardi mai più la sua implementazione (ad es bytes.ToHexStringAtLudicrousSpeed().).
Patridge

2
Ho appena prodotto un'implementazione basata su tabella di ricerca ad alte prestazioni. La sua variante sicura è circa il 30% più veloce dell'attuale leader sulla mia CPU. Le varianti non sicure sono ancora più veloci. stackoverflow.com/a/24343727/445517
CodesInChaos

244

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

35
SoapHexBinary è disponibile da .NET 1.0 ed è in mscorlib. Nonostante sia uno spazio dei nomi divertente, fa esattamente quello che la domanda è stata posta.
Sly Gryphon,

4
Grande scoperta! Si noti che sarà necessario riempire le stringhe dispari con uno 0 iniziale per GetStringToBytes, come l'altra soluzione.
Carter Medlin,

Hai visto l'implementazione pensato? La risposta accettata ha un IMHO migliore.
mfloryan,

6
Interessante vedere l'implementazione di Mono qui: github.com/mono/mono/blob/master/mcs/class/corlib/…
Jeremy

1
SoapHexBinary non è supportato in .NET Core / .NET Standard ...
juFo

141

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:

  1. bytes[i] >> 4estrae il nibble alto di un byte
    bytes[i] & 0xFestrae il nibble basso di un byte
  2. b - 10
    è < 0per i valori b < 10, che diventeranno una cifra decimale
    è >= 0per i valori b > 10, che diventeranno una lettera da Aa F.
  3. L'utilizzo i >> 31su un intero con segno a 32 bit estrae il segno, grazie all'estensione del segno. Sarà -1per i < 0e 0per i >= 0.
  4. Combinando 2) e 3), mostra che (b-10)>>31sarà 0per lettere e -1cifre.
  5. Osservando il caso delle lettere, l'ultimo summand diventa 0ed bè compreso tra 10 e 15. Vogliamo mapparlo su A(65) a F(70), il che implica l'aggiunta di 55 ( 'A'-10).
  6. Osservando il caso delle cifre, vogliamo adattare l'ultimo summand in modo che sia mappato bdall'intervallo da 0 a 9 all'intervallo da 0(48) a 9(57). Ciò significa che deve diventare -7 ( '0' - 55).
    Ora potremmo semplicemente moltiplicare per 7. Ma poiché -1 è rappresentato da tutti i bit che sono 1, possiamo invece usare & -7da (0 & -7) == 0e (-1 & -7) == -7.

Alcune ulteriori considerazioni:

  • Non ho usato una seconda variabile di loop in cui indicizzare c, poiché la misurazione mostra che calcolarla dai è più economico.
  • L'uso esattamente i < bytes.Lengthcome limite superiore del loop consente a JITter di eliminare i controlli dei limitibytes[i] , quindi ho scelto quella variante.
  • Fare bun int permette conversioni non necessarie da e verso byte.

10
E hex stringa byte[] array?
AaA

15
+1 per aver citato correttamente la tua fonte dopo aver invocato quel pizzico di magia nera. Tutti salutano Cthulhu.
Edward,

4
Che dire di stringa in byte []?
Syaiful Nizam Yahya,

9
Bello! Per coloro che hanno bisogno di un output in minuscolo, l'espressione ovviamente cambia in87 + b + (((b-10)>>31)&-39)
eXavier

2
@AaA Hai detto " byte[] array", che significa letteralmente una matrice di array di byte, oppure byte[][]. Mi stavo solo prendendo in giro.
CoolOppo,

97

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.)


21
Ancora più breve: String.Concat (Array.ConvertAll (byte, x => x.ToString ("X2"))
Nestor

14
Ancora più breve: String.Concat (bytes.Select (b => b.ToString ("X2"))) [.NET4]
Allon Guralnek,

14
Risponde solo a metà della domanda.
Sly Gryphon,

1
Perché il secondo ha bisogno di .Net 4? String.Concat è in .Net 2.0.
Polyfun,

2
quei loop di tipo "90's style" sono generalmente più veloci, ma in misura abbastanza trascurabile da non avere importanza nella maggior parte dei contesti. Vale comunque la pena menzionarlo
Austin_Anderson il

69

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 unsafefratelli:

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

Perché la creazione della tabella di ricerca nella versione non sicura scambia i nibble del byte precompilato? Pensavo che l'endianità cambiasse solo l'ordinamento di entità formate da più byte.
Raif Atef,

@RaifAtef Ciò che conta qui non è l'ordine dei stuzzichini. Ma l'ordine delle parole a 16 bit in un numero intero a 32 bit. Ma sto pensando di riscriverlo in modo che lo stesso codice possa essere eseguito indipendentemente dall'endianness.
CodesInChaos

Rileggendo il codice, penso che tu l'abbia fatto perché quando esegui il cast del carattere * in seguito a un uint * e lo assegni (durante la generazione del carattere esadecimale), il runtime / CPU capovolge i byte (poiché uint non viene trattato il come 2 caratteri separati a 16 bit), quindi li stai pre-lanciando per compensare. Ho ragione ? L'endianness è confusa :-).
Raif Atef,

4
Questo risponde solo a metà della domanda ... Che ne dici di una stringa esadecimale in byte?
Narvalex

3
@CodesInChaos Mi chiedo se Spanpuò essere usato ora invece di unsafe??
Konrad

64

È 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 [])


14
Risponde solo a metà della domanda.
Sly Gryphon,

3
Dov'è la seconda parte della risposta?
Sawan,

56

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).


per non parlare del fatto che utilizza meno memoria. Non sono state create stringhe intermedie.
Chochos,

8
Risponde solo a metà della domanda.
Sly Gryphon,

Questo è fantastico perché funziona praticamente su qualsiasi versione di NET, incluso NETMF. Un vincitore!
Jonesome ripristina Monica il

1
La risposta accettata fornisce 2 eccellenti metodi HexToByteArray, che rappresentano l'altra metà della domanda. La soluzione di Waleed risponde alla domanda corrente su come farlo senza creare un numero enorme di stringhe nel processo.
Brendten Eickstaedt,

La nuova stringa (c) copia e riassegna o è abbastanza intelligente da sapere quando può semplicemente avvolgere il carattere []?
jjxtra,

19

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.ToBytee String.Substringse 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.ToBytese hai bisogno di prestazioni. Evitare Non usare altro che String.Substringin 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.Substringe usa StringReaderinvece 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.ToBytecomunque. 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 numeralrappresenta un singolo ottetto usando due cifre (simboli).

Ma allora, perché chiamare StringReader.Readdue 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 jad 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.CopyTosulla 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 jindice che aumenti in incrementi di due paralleli a i? Certo che no, basta moltiplicare iper 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.Substringper 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.Substringin questi casi sarà anche possibile evitarlo.

In effetti, se guardi di String.Substringnuovo, 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 CopyTodirettamente lì dentro per evitare il sovraccarico di chiamata.

String.Substring

  • Caso peggiore: un'allocazione rapida, una copia veloce.
  • Caso migliore: nessuna allocazione, nessuna copia.

Metodo manuale

  • Caso peggiore: due allocazioni normali, una copia normale, una copia veloce.
  • Caso migliore: un'allocazione normale, una copia normale.

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.ToByteed 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.)


errore: {"Impossibile trovare cifre riconoscibili."}
Priya Jagtap,

17

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 0usando & 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...).


16

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:

  • Tabella encoder 512 byte o 1024 byte (il doppio della dimensione se sono necessari sia lettere maiuscole che minuscole)
  • Tabella del decodificatore 256 byte o 64 KiB (una ricerca a carattere singolo o ricerca a doppio carattere)

La mia soluzione utilizza 1024 byte per la tabella di codifica e 256 byte per la decodifica.

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

Codifica

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

Confronto

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

* questa soluzione

Nota

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.


2
L'utilizzo della memoria di 256 byte è trascurabile quando si esegue il codice sul CLR.
dolmen,

9

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

Questo codice presuppone che la stringa esadecimale utilizzi caratteri alfabetici maiuscoli e viene espulsa se la stringa esadecimale utilizza caratteri alfabetici minuscoli. Potrebbe voler fare una conversione "maiuscola" sulla stringa di input per sicurezza.
Marc Novakowski,

È un'osservazione astuta Marc. Il codice è stato scritto per invertire la soluzione di Waleed. La chiamata ToUpper rallenterebbe un po 'l'algoritmo, ma gli consentirebbe di gestire caratteri alfa minuscoli.
Chris F,

3
Convert.ToByte (topChar + bottomChar) può essere scritto come (byte) (topChar + bottomChar)
Amir Rezaei,

Per gestire entrambi i casi senza una grande penalità prestazionale,hexString[i] &= ~0x20;
Ben Voigt,

9

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("-", "")

2
il motivo è la prestazione, quando è necessaria una soluzione ad alte prestazioni. :)
Ricky,

7

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 unsafee spostando la ifsequenza 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.)


1
Per i seguaci di Knuth: l'ho fatto perché ho bisogno di analizzare alcune migliaia di stringhe esadecimali ogni pochi minuti, quindi è importante che sia il più veloce possibile (nel ciclo interno, per così dire). La soluzione di Tomalak non è notevolmente più lenta se non si verificano molti di questi esami.
Ben Mosher,

5

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).


5

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

4

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).


Probabilmente dovresti testare il codice prima di offrirlo per una domanda come questa.
1717

3

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.


2
L'iteratore non è il problema principale di questo codice. Dovresti fare un benchmark b.ToSting("X2").
dolmen,

2

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

se Source == nullo Source.Length == 0abbiamo un problema signore!
Andrei Krasutski,

2

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

2

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

2

Questa versione di ByteArrayToHexViaByteManipulation potrebbe essere più veloce.

Dai miei rapporti:

  • ByteArrayToHexViaByteManipulation3: 1,68 tick medi (oltre 1000 corse), 17,5X
  • ByteArrayToHexViaByteManipulation2: 1,73 tick medi (oltre 1000 corse), 16,9X
  • ByteArrayToHexViaByteManipulation: 2,90 tick medi (oltre 1000 corse), 10,1X
  • ByteArrayToHexViaLookupAndShift: 3,22 tick medi (oltre 1000 corse), 9,1X
  • ...

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

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

2

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 StringBuildermetodi 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.


Hmm, dovrei davvero ottimizzarlo Char[]e usarlo Charinternamente anziché
ints

Per C #, l'inizializzazione delle variabili in cui vengono utilizzate, anziché all'esterno del ciclo, è probabilmente preferita per consentire al compilatore di ottimizzare. Ottengo prestazioni equivalenti in entrambi i casi.
Peteter,

2

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 offsetinvece di offsete offset + 1potrebbe 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.


1

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

1

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

1

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

1
HexStringToByteArray ("09") restituisce 0x02, il che è male
CoperNick,

1

Un altro modo è utilizzare stackallocper 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);
}

1

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 foreachper 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:

  1. Le penalità per l'indicizzazione in una stringa per eseguire la ricerca rispetto a un array di caratteri sono significative nel test di file di grandi dimensioni.
  2. Le penalità per l'utilizzo di un StringBuilder di capacità nota rispetto a un array di caratteri di dimensioni note per creare la stringa sono ancora più significative.

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



1

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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.