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




visual studio byte to hex (24)

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

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


Per prestazioni vorrei andare 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 al metodo sono costose. Se viene effettuato un qualche tipo di controllo sia su dati di input o di output (potrebbe essere CRC, checksum o qualsiasi altra cosa), si if (b == 255)...potrebbe saltare e quindi anche il metodo chiama del tutto.

Usare offset++e offsetinvece di offsete offset + 1potrebbe dare qualche beneficio teorico ma sospetto che il compilatore gestisca questo 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 è fuori dalla mia testa e non è stato testato o benchmark.


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

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 ancora più varianti per farlo, per esempio here .

La conversione inversa sarebbe andata 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'utilizzo della Substring è l'opzione migliore in combinazione con Convert.ToByte . Vedi questa risposta per maggiori informazioni. Se hai bisogno di prestazioni migliori, devi evitare Convert.ToByte prima di poter rilasciare SubString .


Questa è una risposta alla revisione 4 della share di share (e successive modifiche).

Farò il caso che questa modifica sia errata e spieghi perché potrebbe essere ripristinata. Lungo la strada, potresti imparare una o due cose su alcuni interni, e vedere ancora un altro esempio di cosa sia veramente l'ottimizzazione prematura e come possa morderti.

tl; dr: usa solo String.Substring e String.Substring se sei di fretta ("Codice originale" sotto), è la migliore combinazione se non vuoi reimplementare Convert.ToByte . Usa qualcosa di più avanzato (vedi altre risposte) che non usa Convert.ToByte se hai bisogno di prestazioni. Non utilizzare nient'altro che String.Substring in combinazione con Convert.ToByte , a meno che qualcuno abbia qualcosa di interessante da dire a riguardo nei commenti di questa risposta.

avviso: questa risposta potrebbe diventare obsoleta se nel framework è implementato un overload di Convert.ToByte(char[], Int32) . È improbabile che ciò accada presto.

Come regola generale, non mi piace molto dire "non ottimizzarlo prematuramente", perché nessuno sa quando è "prematura". L'unica cosa che devi considerare quando decidi se ottimizzare o meno è: "Ho il tempo e le risorse per investigare appropriatamente gli 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 c'è un bisogno reale, allora ne prenderai 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 String.Substring e utilizza invece un StringReader . Il motivo dato è:

Modifica: puoi migliorare le prestazioni per le stringhe lunghe usando un parser single pass, in questo modo:

Bene, guardando il codice di riferimento per String.Substring , è già chiaramente "single-pass"; e perché non dovrebbe essere? Funziona a livello di byte, non su coppie surrogate.

Tuttavia, alloca una nuova stringa, ma è necessario assegnarne una per passare comunque a Convert.ToByte . Inoltre, la soluzione fornita nella revisione alloca ancora un altro oggetto su ogni iterazione (l'array a due char); puoi tranquillamente mettere questa allocazione fuori dal ciclo e riutilizzare la matrice 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 numeral esadecimale rappresenta un singolo ottetto utilizzando due cifre (simboli).

Ma allora, perché chiamare StringReader.Readdue volte? Basta chiamare il suo secondo sovraccarico e chiedergli di leggere due caratteri nella matrice di 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;
    }

Quello che ti rimane è un lettore di stringhe il cui unico "valore" aggiunto è un indice parallelo (interno _pos) che potresti aver dichiarato tu stesso ( 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 overhead per mantenere valori che non abbiamo bisogno.

Quindi, rimuovi già il lettore di stringhe e chiama CopyTote 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 jindice che aumenti in passi di due paralleli a i? Certo che no, basta moltiplicare iper due (che il compilatore dovrebbe essere in grado di ottimizzare in 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 appare la soluzione ora? Esattamente come all'inizio, solo che invece di utilizzare String.Substringper allocare la stringa e copiare i dati su di essa, si sta utilizzando un array intermedio a cui si copiano i numeri esadecimali, quindi si alloca la stringa da soli e si copia nuovamente i dati da l'array e nella stringa (quando lo si passa nel costruttore di stringhe). La seconda copia potrebbe essere ottimizzata se la stringa è già nel pool interno, ma String.Substringin tal caso sarà anche possibile evitarla.

Infatti, se si guarda di String.Substringnuovo, si vede che utilizza una conoscenza interna di basso livello di come le stringhe sono costruite per allocare la stringa più velocemente di quanto si possa normalmente fare, e incorpora lo stesso codice utilizzato CopyTodirettamente lì per evitare l'overhead di chiamata.

String.Substring

  • Peggiore caso: un'allocazione rapida, una copia veloce.
  • Best-case: nessuna allocazione, nessuna copia.

Metodo manuale

  • Peggiore caso: due allocazioni normali, una copia normale, una copia veloce.
  • Best-case: un'allocazione normale, una copia normale.

Conclusione? Se si desidera utilizzareConvert.ToByte(String, Int32) (perché non si vuole ri-implementare tale funzionalità da soli), non sembra essere un modo per battere String.Substring; tutto ciò che fai è correre in cerchio, reinventando la ruota (solo con materiali sub-ottimali).

Si noti che l'uso Convert.ToByteed String.Substringè una scelta perfettamente valida se non si ha bisogno di prestazioni estreme. Ricorda: scegli solo un'alternativa se hai tempo e risorse per studiare come funziona correttamente.

Se ci fosse un Convert.ToByte(char[], Int32), le cose sarebbero diverse ovviamente (sarebbe possibile fare quello che ho descritto sopra ed evitarle completamente String).

Sospetto che anche le persone che dichiarano prestazioni migliori di "evitare String.Substring" 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 la fonte di riferimento sia aggiornata, presumo che lo sia.

Ora, tutto sembra buono e logico, si spera anche 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ì!

Puntelli su Partridge per il framework bench, è facile da hackerare. L'input utilizzato è il seguente hash SHA-1 ripetuto 5000 volte per creare una stringa lunga 100.000 byte.

209113288F93A9AB8E474EA78D899AFDBB874355

Divertiti! (Ma ottimizza con moderazione.)


Complemento per rispondere con @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 è quello di 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 hi = ch - 65 + 10 + 7;cui è hi = ch - 48(questo è causa di 0xffffffff & 7).

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

Per 'a' .. 'f' abbiamo numeri grandi quindi dobbiamo sottrarre 32 dalla versione di default 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:;<=>[email protected]).


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

Dagli sviluppatori Microsoft, una semplice conversione:

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, i drogati delle prestazioni urleranno con gli enumeratori. È possibile ottenere prestazioni ottimali 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 postate qui finora. Non limitarti a credermi sulla parola ... prova le prestazioni di ogni routine e ispezionarne il codice CIL.


Puoi 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: BitConverter.ToString Method (Byte [])


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 test di benchmark inizializzando l'alfabeto ogni volta che la funzione di conversione chiamata è errata, l'alfabeto deve essere const (per stringa) o statico readonly (per char []). Quindi la conversione in ordine alfabetico del byte [] in stringa diventa veloce quanto le versioni di manipolazione dei byte.

E ovviamente il test deve essere compilato in Release (con ottimizzazione) e con l'opzione di debug "Soppressione dell'ottimizzazione JIT" disattivata (lo stesso vale per "Abilita solo il mio codice" se il codice deve essere debuggabile).


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 (due volte la dimensione se sono necessari entrambi i casi maiuscole e minuscole)
  • Tabella dei decodificatori 256 byte o 64 KiB (una ricerca char singola 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 personaggio ha un valore troppo alto> 256). I metodi per de / codificare i flussi o gli array dovrebbero essere implementati, questa è solo una dimostrazione del concetto.


Un altro modo è stackallocquello di 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);
}

Non ho ricevuto il codice che hai suggerito di lavorare, Olipro. hex[i] + hex[i+1]apparentemente restituito un int.

L'ho fatto, tuttavia, ho avuto qualche successo prendendo alcuni spunti dal codice Waleeds e martellandolo insieme. È brutto da morire ma sembra funzionare e funziona al 1/3 delle volte rispetto agli altri secondo i miei test (usando il meccanismo di test dei Ponti). A seconda della dimensione dell'input. Cambiare il carattere?: S per separare 0-9 per primo probabilmente darebbe 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;
}

Se vuoi una maggiore flessibilità rispetto a BitConverter , ma non vuoi quei goffi cicli espliciti in stile anni '90, allora puoi fare:

String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));

Oppure, se stai usando .NET 4.0:

String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));

(Quest'ultimo da un commento sul post originale.)


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

Entrerò in questa competizione un po 'fastidiosa perché ho una risposta che usa anche il bit-trick per decodificare gli esadecimali. Si noti che l'uso di matrici di caratteri può essere ancora più veloce, poiché i StringBuildermetodi di chiamata richiederanno anche del 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.


Questo è un grande post. Mi piace la soluzione di Waleed. Non ho eseguito il test di Patridge ma sembra abbastanza veloce. Avevo anche bisogno del processo inverso, convertendo una stringa esadecimale in un array di byte, quindi l'ho scritto come inversione della soluzione di Waleed. Non sono sicuro se sia più veloce della soluzione originale di Tomalak. Ancora una volta, non ho eseguito il processo inverso tramite 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;
}

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

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

Metodi di estensione (disclaimer: 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. Utilizza una delle tre soluzioni di Tomalak (l'ultima è un metodo di estensione su una stringa).


Un altro approccio basato sulla tabella di ricerca. Questo utilizza solo una tabella di ricerca per ogni byte, invece di 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), quelli avevano le stesse prestazioni o erano leggermente più lenti di questa variante.

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

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

Ho appena incontrato lo stesso problema oggi e ho trovato 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 su Hex String (vedere il post di PZahra). Ho modificato leggermente il codice per rimuovere il prefisso 0x.

Ho eseguito alcuni test delle prestazioni sul codice ed era quasi otto volte più veloce rispetto all'utilizzo di BitConverter.ToString () (il più veloce secondo il post di Patridge).


Due mashup che piegano le due operazioni di nibble in uno solo.

Probabilmente una versione 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-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 invertire:

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

Quando si scrive codice crittografico, è comune evitare i rami dipendenti dai dati e le ricerche di tabelle per garantire che il runtime non dipenda dai dati, poiché i tempi dipendenti dai dati possono portare ad attacchi di canale laterale.

È anche piuttosto 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 pasticcio:

  1. bytes[i] >> 4 estrae il nibble 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'utilizzo di i >> 31 su un intero con segno a 32 bit estrae il segno, grazie all'estensione del segno. Sarà -1 per i < 0 e 0 per i >= 0 .
  4. Combinando 2) e 3), mostra che (b-10)>>31 sarà 0 per le lettere e -1 per le cifre.
  5. Guardando il caso per lettere, l'ultimo summe diventa 0 , e b è nell'intervallo da 10 a 15. Vogliamo mapparlo a A (65) a F (70), il che implica l'aggiunta di 55 ( 'A'-10 ) .
  6. Guardando il caso per le cifre, vogliamo adattare l'ultimo summe in modo che mappa b nell'intervallo da 0 a 9 nell'intervallo da 0 (48) a 9 (57). Ciò significa che deve diventare -7 ( '0' - 55 ).
    Ora potremmo semplicemente moltiplicare con 7. Ma poiché -1 è rappresentato dal fatto che tutti i bit sono 1, possiamo invece usare & -7 poiché (0 & -7) == 0 e (-1 & -7) == -7 .

Alcune ulteriori considerazioni:

  • Non ho usato una variabile del secondo ciclo per indicizzare in c , poiché la misurazione mostra che calcolarlo da i è più economico.
  • Usando esattamente i < bytes.Length come limite superiore del ciclo consente a JITter di eliminare i controlli dei limiti sui bytes[i] , quindi ho scelto questa variante.
  • Fare b int consente conversioni non necessarie da e verso byte.




hex