c# - str - visual studio byte to hex




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

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


Analisi di performance

Nota: nuovo leader dal 20/08/2015.

Ho eseguito ciascuno dei vari metodi di conversione attraverso alcuni test di performance Stopwatch crudi, un'esecuzione 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 misure sono in tick ( 10.000 tick = 1 ms ) e tutte le note relative vengono confrontate con l'implementazione [più lenta] di StringBuilder . Per il codice utilizzato, vedi sotto o il repository del framework di test dove ora manterrò il codice per eseguirlo.

disconoscimento

ATTENZIONE: non fare affidamento su queste statistiche per nulla di concreto; sono semplicemente una serie di esempi di dati di esempio. Se hai davvero bisogno di prestazioni di alto livello, prova questi metodi in un ambiente rappresentativo delle tue esigenze di produzione con i dati rappresentativi di ciò che userai.

risultati

  • Ricerca per byte unsafe (tramite CodesInChaos) (aggiunto al test repo da airbreather )
    • Testo: 4,727,85 (105,2X)
    • Frase: 0,28 (99,7 X)
  • Ricerca per byte (tramite CodesInChaos)
    • Testo: 10,853,96 (45,8 volte più veloce)
    • Frase: 0,65 (42,7 X più veloce)
  • Byte Manipulation 2 (via CodesInChaos)
    • Testo: 12,967,69 (38,4 volte più veloce)
    • Frase: 0,73 (37,9 volte più veloce)
  • Manipolazione dei byte (tramite Waleed Eissa)
    • Testo: 16,856,64 (29,5 volte più veloce)
    • Frase: 0,70 (39,5 volte più veloce)
  • Ricerca / spostamento (via Nathan Moinvaziri)
    • Testo: 23,201.23 (21.4X più veloce)
    • Frase: 1,24 (22,3 volte più veloce)
  • Ricerca di nibble (via Brian Lambert)
    • Testo: 23,879,41 (20,8 volte più veloce)
    • Frase: 1,15 (23,9 volte più veloce)
  • BitConverter (via Tomalak)
    • Testo: 113,269,34 (4,4 volte più veloce)
    • Frase: 9,98 (più veloce 2,8 volte)
  • {SoapHexBinary}.ToString (tramite Mykroft)
    • Testo: 178.601.39 (2.8X più veloce)
    • Frase: 10,68 (2,6X più veloce)
  • {byte}.ToString("X2") (utilizzando foreach ) (derivato dalla risposta di Will Dean)
    • Testo: 308,805,38 (2,4 volte più veloce)
    • Frase: 16,89 (2,4 volte più veloce)
  • {byte}.ToString("X2") (utilizzando {IEnumerable}.Aggregate , richiede System.Linq) (via Mark)
    • Testo: 352,828.20 (2.1X più veloce)
    • Frase: 16,87 (2,4 volte più veloce)
  • Array.ConvertAll (using string.Join ) (via Will Dean)
    • Testo: 675,451,57 (1,1X più veloce)
    • Frase: 17,95 (2,2 volte più veloce)
  • Array.ConvertAll (utilizzando string.Concat , richiede .NET 4.0) (tramite Will Dean)
    • Testo: 752.078.70 (1.0X più veloce)
    • Frase: 18.28 (2.2X più veloce)
  • {StringBuilder}.AppendFormat (utilizzando foreach ) (via Tomalak)
    • Testo: 672.115.77 (1.1X più veloce)
    • Frase: 36,82 (più veloce 1,1 volte)
  • {StringBuilder}.AppendFormat (utilizzando {IEnumerable}.Aggregate , richiede System.Linq) (derivato dalla risposta di Tomalak)
    • Testo: 718,380,63 (1,0 volte più veloce)
    • Frase: 39.71 (1.0X più veloce)

Le tabelle di ricerca hanno preso il comando sulla manipolazione dei byte. Fondamentalmente, c'è una qualche forma di precomputing che ogni dato nibble o byte sarà in esadecimale. Quindi, mentre sfogli i dati, devi semplicemente cercare la parte successiva per vedere quale stringa esagonale sarebbe. Questo valore viene quindi aggiunto all'output di stringa risultante in qualche modo. Per molto tempo la manipolazione dei byte, potenzialmente più difficile da leggere da parte di alcuni sviluppatori, è stata l'approccio più performante.

La tua migliore scommessa sarà comunque 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 che consuma più memoria.

Codice di prova

Sentiti libero di giocare con il codice di prova che ho usato. Una versione è inclusa qui, ma sentiti libero di clonare il repository e aggiungere i tuoi metodi. Invia una richiesta di pull se trovi qualcosa di interessante o vuoi contribuire a migliorare il framework di testing che usa.

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

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

Aggiornamento (2011-10-05)

Aggiunto string.Concat Array.ConvertAll variant per completezza (richiede .NET 4.0). Alla pari con string.Join versione.

Aggiornamento (2012-02-05)

Il repo di prova include più varianti come StringBuilder.Append(b.ToString("X2")) . Nessuno ha stravolto i risultati. foreach è più veloce di {IEnumerable}.Aggregate , ad esempio, ma BitConverter vince ancora.

Aggiornamento (2012-04-03)

Aggiunta la risposta SoapHexBinary di SoapHexBinary all'analisi, che ha occupato il terzo posto.

Aggiornamento (2013-01-15)

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

Aggiornamento (2013-05-23)

Aggiunta la risposta di ricerca di Nathan Moinvaziri e la variante del blog di Brian Lambert. Entrambi piuttosto veloci, ma non in testa alla macchina di prova che ho usato (AMD Phenom 9750).

Aggiornamento (2014-07-31)

Aggiunta la nuova risposta di ricerca basata su byte @ CodesInChaos. Sembra aver preso l'iniziativa sia nei test di frase che nei test di testo completo.

Aggiornamento (20-08-2010)

Aggiunte airbreather ottimizzazioni airbreather e la variante unsafe al repository di questa risposta . Se vuoi giocare nel gioco non sicuro, puoi ottenere enormi guadagni in termini di prestazioni rispetto a tutti i precedenti vincitori, sia su stringhe brevi che su testi di grandi dimensioni.


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

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 .


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


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


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

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


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

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


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

E per l'inserimento in una stringa SQL (se non stai usando i parametri di comando):

public static String ByteArrayToSQLHexString(byte[] Source)
{
    return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}

In termini di velocità, questo sembra essere meglio di qualsiasi altra cosa qui:

  public static string ToHexString(byte[] data) {
    byte b;
    int i, j, k;
    int l = data.Length;
    char[] r = new char[l * 2];
    for (i = 0, j = 0; i < l; ++i) {
      b = data[i];
      k = b >> 4;
      r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
      k = b & 15;
      r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
    }
    return new string(r);
  }

Non 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

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

Ecco il mio colpo. Ho creato una coppia di classi di estensione per estendere stringhe e byte. Nel test di file di grandi dimensioni, le prestazioni sono paragonabili a Byte Manipulation 2.

Il codice seguente per ToHexString è un'implementazione ottimizzata dell'algoritmo di ricerca e spostamento. È quasi identico a quello di Behrooz, ma si scopre che usa un foreachiterare e un contatore è più veloce di un indicizzazione esplicita for.

Si trova al 2 ° posto dietro Byte Manipulation 2 sulla mia macchina ed è un codice molto leggibile. I seguenti risultati del test sono anche di interesse:

ToHexStringCharArrayWithCharArrayLookup: 41,589,69 tick medi (oltre 1000 esecuzioni), 1,5X ToHexStringCharArrayWithStringLookup: 50,764.06 tick medi (oltre 1000 esecuzioni), 1,2X ToHexStringStringBuilderWithCharArrayLookup: 62,812,87 tick medi (oltre 1000 corse), 1,0X

Sulla base dei risultati sopra riportati, sembra sicuro concludere che:

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

Ecco il codice:

using System;

namespace ConversionExtensions
{
    public static class ByteArrayExtensions
    {
        private readonly static char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

        public static string ToHexString(this byte[] bytes)
        {
            char[] hex = new char[bytes.Length * 2];
            int index = 0;

            foreach (byte b in bytes)
            {
                hex[index++] = digits[b >> 4];
                hex[index++] = digits[b & 0x0F];
            }

            return new string(hex);
        }
    }
}


using System;
using System.IO;

namespace ConversionExtensions
{
    public static class StringExtensions
    {
        public static byte[] ToBytes(this string hexString)
        {
            if (!string.IsNullOrEmpty(hexString) && hexString.Length % 2 != 0)
            {
                throw new FormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");
            }

            hexString = hexString.ToUpperInvariant();
            byte[] data = new byte[hexString.Length / 2];

            for (int index = 0; index < hexString.Length; index += 2)
            {
                int highDigitValue = hexString[index] <= '9' ? hexString[index] - '0' : hexString[index] - 'A' + 10;
                int lowDigitValue = hexString[index + 1] <= '9' ? hexString[index + 1] - '0' : hexString[index + 1] - 'A' + 10;

                if (highDigitValue < 0 || lowDigitValue < 0 || highDigitValue > 15 || lowDigitValue > 15)
                {
                    throw new FormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");
                }
                else
                {
                    byte value = (byte)((highDigitValue << 4) | (lowDigitValue & 0x0F));
                    data[index / 2] = value;
                }
            }

            return data;
        }
    }
}

Di seguito sono riportati i risultati del 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. Le prove di test che hanno esercitato il mio codice sono ByteArrayToHexViaOptimizedLookupAndShift e HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte è stato preso 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 una rappresentazione di stringa esadecimale

ByteArrayToHexViaByteManipulation2: 39.366.64 tick medi (oltre 1000 corse), 22.4X

ByteArrayToHexViaOptimizedLookupAndShift: 41.588,64 tick medi (oltre 1000 corse), 21.2X

ByteArrayToHexViaLookup: 55.509,56 tick medi (oltre 1000 corse), 15,9X

ByteArrayToHexViaByteManipulation: 65,349.12 tick medi (oltre 1000 corse), 13,5X

ByteArrayToHexViaLookupAndShift: 86.926,87 tick medi (oltre 1000 corse), 10.2X

ByteArrayToHexStringViaBitConverter: 139,353,73 tick medi (oltre 1000 corse), 6,3X

ByteArrayToHexViaSoapHexBinary: 314,598,77 tick medi (oltre 1000 corse), 2,8X

ByteArrayToHexStringViaStringBuilderForEachByteToString: 344,264,63 tick medi (oltre 1000 corse), 2,6X

ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382.623,44 tick medi (oltre 1000 corse), 2,3X

ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818.111,95 tick medi (oltre 1000 corse), 1.1X

ByteArrayToHexStringViaStringConcatArrayConvertAll: 839.244,84 tick medi (oltre 1000 corse), 1.1X

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

ByteArrayToHexStringViaStringJoinArrayConvertAll: 882.710.28 tick medi (oltre 1000 corse), 1.0X


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

Non per aggrapparmi alle molte risposte qui, ma ho trovato una soluzione abbastanza ottimizzata (~ 4.5x meglio di quella accettata), semplice da usare con il parser di stringa esadecimale. Innanzitutto, l'output dei miei test (il primo batch è la mia implementazione):

Give me that string:
04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939f

Time to parse 100,000 times: 50.4192 ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F

Accepted answer: (StringToByteArray)
Time to parse 100000 times: 233.1264ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F

With Mono's implementation:
Time to parse 100000 times: 777.2544ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F

With SoapHexBinary:
Time to parse 100000 times: 845.1456ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F

Le linee base64 e 'BitConverter'd' sono lì per testare la correttezza. Si noti che sono uguali.

L'implemento:

public static byte[] ToByteArrayFromHex(string hexString)
{
  if (hexString.Length % 2 != 0) throw new ArgumentException("String must have an even length");
  var array = new byte[hexString.Length / 2];
  for (int i = 0; i < hexString.Length; i += 2)
  {
    array[i/2] = ByteFromTwoChars(hexString[i], hexString[i + 1]);
  }
  return array;
}

private static byte ByteFromTwoChars(char p, char p_2)
{
  byte ret;
  if (p <= '9' && p >= '0')
  {
    ret = (byte) ((p - '0') << 4);
  }
  else if (p <= 'f' && p >= 'a')
  {
    ret = (byte) ((p - 'a' + 10) << 4);
  }
  else if (p <= 'F' && p >= 'A')
  {
    ret = (byte) ((p - 'A' + 10) << 4);
  } else throw new ArgumentException("Char is not a hex digit: " + p,"p");

  if (p_2 <= '9' && p_2 >= '0')
  {
    ret |= (byte) ((p_2 - '0'));
  }
  else if (p_2 <= 'f' && p_2 >= 'a')
  {
    ret |= (byte) ((p_2 - 'a' + 10));
  }
  else if (p_2 <= 'F' && p_2 >= 'A')
  {
    ret |= (byte) ((p_2 - 'A' + 10));
  } else throw new ArgumentException("Char is not a hex digit: " + p_2, "p_2");

  return ret;
}

Ho provato alcune cose con unsafee spostando la ifsequenza (chiaramente ridondante) da personaggio a nibble a un altro metodo, ma questo è stato il più veloce.

(Ammetto che questo risponde a metà della domanda: ho sentito che la conversione string-> byte [] era sottorappresentata, mentre il byte [] -> l'angolo della stringa sembra essere ben coperto.


Perché renderlo complesso? Questo è semplice in Visual Studio 2008:

C #:

string hex = BitConverter.ToString(YourByteArray).Replace("-", "");

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")

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




hex