Domanda

Come puoi convertire un array di byte in una stringa esadecimale e viceversa?

È stato utile?

Soluzione

In entrambi:

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

Esistono ancora più varianti nel 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 di Substring è l'opzione migliore in combinazione con Convert.ToByte. Vedi questa risposta per ulteriori informazioni. Se hai bisogno di prestazioni migliori, devi evitare SubString prima di poter rilasciare <=>.

Altri suggerimenti

Analisi delle prestazioni

Nota: nuovo leader dal 20-08-2015.

Ho eseguito ciascuno dei vari metodi di conversione attraverso alcuni test di prestazione Stopwatch 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'implementazione [più lenta] StringBuilder. Per il codice utilizzato, vedere di seguito o il repo framework di test dove ora mantengo il codice per l'esecuzione di questo.

responsabilità

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 prim'ordine, 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 sulla 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, è stato 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 consumerebbe 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 repo e di 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 (<=>) a /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Aggiungi il nome di quel metodo al <=> valore restituito nella stessa classe.
  3. Assicurati di eseguire la versione di input desiderata, frase o testo, alternando i commenti in <=> in quella stessa classe.
  4. Premi F5 e attendi l'output (un dump HTML viene generato anche nella cartella / bin).
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 la variante <=> <=> per completezza (richiede .NET 4.0). Alla pari con <=> versione.

Aggiornamento (05/02/2012)

Il repository di prova include più varianti come <=>. Nessuno ha stravolto i risultati. <=> è più veloce di <=>, ad esempio, ma <=> vince ancora.

Aggiornamento (03-04-2012)

Aggiunta la <=> risposta di Mykroft 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 l'iniziativa sia per i test delle frasi che per i test full-text.

Aggiornamento (20-08-2015)

Aggiunte ottimizzazioni di airbreather e <=> variante a questa repository delle risposte . 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.

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

Alcune ulteriori considerazioni:

  • Non ho usato una seconda variabile del ciclo per indicizzare in <=>, poiché la misurazione mostra che calcolarla da <=> è più economica.
  • L'uso esattamente <=> come limite superiore del loop consente a JITter di eliminare i controlli dei limiti su <=>, quindi ho scelto quella variante.
  • Rendere <=> un int consente conversioni non necessarie da e verso byte.

Se vuoi maggiore flessibilità rispetto a 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 testato varianti di questo usando ushort, struct{char X1, X2}, struct{byte X1, X2} nella tabella di ricerca.

A seconda del target di compilazione (x86, X64), entrambi avevano le stesse prestazioni o erano leggermente più lenti di questa variante.


E per prestazioni ancora più elevate, il suo unsafe fratello:

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

Puoi usare il metodo BitConverter.ToString:

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

Output:

  

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: post sul forum byte [] Array to Hex String (vedi il post di PZahra). Ho modificato un po 'il codice per rimuovere il prefisso 0x.

Ho eseguito alcuni test delle prestazioni del codice ed è stato quasi otto volte più veloce rispetto all'utilizzo di BitConverter.ToString () (il più veloce in base al post di Patridge).

Questo problema potrebbe anche essere risolto utilizzando una tabella di ricerca.Ciò richiederebbe una piccola quantità di memoria statica sia per il codificatore che per il decodificatore.Questo metodo sarà comunque veloce:

  • ECCODER Tabella 512 byte o 1024 byte (due volte più grande se è necessario sia superiore che minuscolo)
  • Tabella decodificatore 256 byte o 64 kib (una singola look-up di carbone o doppia ricerca Char)

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 flussi o array, questa è solo una prova di concetto.

Questa è una risposta a revisione 4 di Risposta molto popolare di Tomalak (e successive modifiche).

Caso che questa modifica sia errata 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: usa Convert.ToByte e String.Substring se sei di fretta (" codice originale " sotto), è la combinazione migliore se non desideri implementare nuovamente Convert.ToByte(char[], Int32). Usa qualcosa di più avanzato (vedi altre risposte) che non usa StringReader se hai bisogno delle prestazioni. non utilizzare qualcos'altro oltre a numeral in combinazione con StringReader.Read, a meno che qualcuno non abbia qualcosa di interessante da dire al riguardo nei commenti di questa risposta.

avviso: questa risposta può diventare obsoleta se viene implementato un _pos sovraccarico nel framework. È improbabile che ciò accada presto.

Come regola generale, non mi piace molto dire " non ottimizzare prematuramente " ;, perché nessuno sa quando " prematuro " è. L'unica cosa che devi considerare quando decidi se ottimizzare o meno è: & Quot; Ho il tempo e le risorse per investigare correttamente gli approcci di ottimizzazione? & Quot ;. In caso contrario, è troppo presto, aspetta che il tuo progetto sia più maturo o fino a quando non hai bisogno delle prestazioni (se ce n'è una reale necessità, farai il tempo). Nel frattempo, fai la cosa più semplice che potrebbe funzionare invece.

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 j e usa invece _length. Il motivo indicato è:

  

Modifica: puoi migliorare le prestazioni per stringhe lunghe usando una sola   passa parser, in questo modo:

Bene, guardando il codice di riferimento per _s , è chiaramente " single-pass " già; 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 comunque a Read. 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 String.CopyTo rappresenta un singolo ottetto usando due cifre (simboli).

Ma allora, perché chiamare CopyTo 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 & è stato aggiunto; valore " è un indice parallelo (interno i) che potresti aver dichiarato (come Convert.ToByte(String, Int32) ad esempio), una variabile di lunghezza ridondante (interna String) e un riferimento ridondante alla stringa di input (interna <=>) . In altre parole, è inutile.

Se ti chiedi come <=> " legge " ;, basta guardare il codice , tutto ciò che fa è chiamare <=> sulla stringa di input. Il resto è solo un sovraccarico di contabilità per mantenere i valori di cui non abbiamo bisogno.

Quindi, rimuovi già il lettore di stringhe e chiama <=> te stesso; è 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 <=> indice che passi in incrementi di due paralleli a <=>? Certo che no, basta moltiplicare <=> 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;
    }

Che aspetto ha la soluzione adesso? Esattamente come all'inizio, solo invece di utilizzare <=> per allocare la stringa ae poi copia i dati su di esso, stai usando un array intermedio in cui copi i numeri esadecimali, quindi alloca tu stesso la stringa e copia i dati di nuovo dall'array e nella stringa (quando passalo nel costruttore di stringhe). La seconda copia potrebbe essere ottimizzata se la stringa è già nel pool interno, ma in questi casi <=> sarà anche in grado di evitarlo.

In effetti, se si guarda di nuovo <=>, si nota che utilizza una conoscenza interna di basso livello su come sono costruite le stringhe per allocare la stringa più velocemente di quanto si possa fare normalmente e incorpora lo stesso codice utilizzato di <=> direttamente lì dentro per evitare l'overhead della chiamata.

<=>

  • 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 usare <=> (perché non vuoi implementare di nuovo quella funzionalità), non sembra esserci un modo per battere <=>; tutto ciò che fai è correre in cerchio, reinventando la ruota (solo con materiali non ottimali).

Nota che l'uso di <=> e <=> è 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 <=>, le cose sarebbero ovviamente diverse (sarebbe possibile fare ciò che ho descritto sopra ed evitare completamente <=>).

Sospetto che le persone che riportano prestazioni migliori di " evitando <=> " evita anche <=>, 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 la fonte di riferimento sia aggiornata, presumo che lo sia.

Ora, tutto sembra buono 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

Buon divertimento! (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 supporta anche lettere minuscole

hi = hi + 10 + ((hi >> 31) & 7); è uguale a:

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

Per '0' .. '9' è uguale a hi = ch - 65 + 10 + 7; che è hi = ch - 48 (ciò è dovuto a 0xffffffff & 7).

Per 'A' .. 'F' è hi = ch - 65 + 10; (questo a causa di 0x00000000 & 7).

Per 'a' .. 'f' dobbiamo numeri grandi, quindi dobbiamo sottrarre 32 dalla versione predefinita facendo alcuni bit 0 usando 'A'.

65 è il codice per '0'

48 è il codice per '9'

7 è il numero di lettere tra ...456789:;<=>?@ABCD... e <=> nella tabella ASCII (<=>).

Questo è un ottimo 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;
}

Perché renderlo complesso? Questo è semplice in Visual & & Nbsp; Studio & & Nbsp; 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. Nota che sono uguali.

L'implementazione:

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 sequenza (chiaramente ridondante) da carattere a crocchette if in un altro metodo, ma questo è stato il più veloce che ha ottenuto.

(Ammetto che questo risponde a metà della domanda. Ho sentito che la conversione di stringa - > byte [] era sottorappresentata, mentre l'angolo di 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;
        }
    }
}

tra Per il test di benchmark che inizializza 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 " Sopprimere l'ottimizzazione JIT " disattivato (lo stesso per " Abilita solo il mio codice " se il codice deve essere debuggable).

Funzione inversa per codice Wissaed Eissa (Hex String To Byte Array):

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

        return b;
    }

Funzione Wissaed Eissa 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. Utilizzare uno dei Le tre soluzioni di Tomalak (l'ultima è un metodo di estensione su una stringa).

Dagli sviluppatori di Microsoft, una conversione semplice e piacevole:

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 urleranno su di esso usando gli enumeratori. Puoi ottenere le massime prestazioni con una versione migliorata della risposta originale di Tomolak:

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

   for(int i=0; i < ga.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.

In termini di velocità, questo sembra essere migliore 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 ha restituito un int.

Tuttavia, ho avuto un certo successo prendendo alcuni suggerimenti dal codice di 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. Cambiare prima?? S per separare prima 0-9 probabilmente produrrebbe 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:

  • 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 questa 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 di manipolazione dei bit poiché ho una risposta che usa anche la manipolazione dei bit per decodificare esadecimali. Tieni presente che l'utilizzo di array di caratteri potrebbe essere ancora più veloce poiché anche la chiamata di StringBuilder metodi richiederà 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 da codice Java.

Per le prestazioni andrei con la soluzione drphrozens. Una piccola ottimizzazione per il decodificatore potrebbe essere quella di utilizzare una tabella per entrambi i caratteri per eliminare & Quot; & Lt; & Lt; 4 quot &;.

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) il if (b == 255)... potrebbe essere ignorato e quindi anche il metodo chiama del tutto.

L'uso di offset++ e offset invece di offset + 1 e <=> 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.

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

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

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

Versione decadente di linq-with-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 tentativo. 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 risulta usare un 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 corse), 1,5X ToHexStringCharArrayWithStringLookup: 50.764,06 tick medi (oltre 1000 corse), 1,2X ToHexStringStringBuilderWithCharArrayLookup: 62.812,87 tick medi (oltre 1000 corse), 1.0X

Sulla base dei risultati precedenti sembra sicuro concludere che:

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

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   corre), 10.2X

     

ByteArrayToHexStringViaBitConverter: media 139.353,73   tick (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 media   tick (oltre 1000 corse), 1.1X

     

ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867.303,98   tick medi (oltre 1000 corse), 1.0X

     

ByteArrayToHexStringViaStringJoinArrayConvertAll: 882.710,28 media   tick (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;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top