1461 votes

Comment convertir un tableau de bytes en une chaîne hexadécimale, et vice versa ?

Comment pouvez-vous convertir un tableau de bytes en une chaîne hexadécimale et vice versa?

8 votes

La réponse acceptée ci-dessous semble allouer une quantité horrible de chaînes dans la conversion de chaînes en octets. Je me demande comment cela impacte les performances

9 votes

La classe SoapHexBinary fait exactement ce que vous voulez, je pense.

0 votes

Je viens de commencer à utiliser le C# aujourd'hui et je vois qu'il y a environ 100 pages de réponses ci-dessous à cette question. Est-ce à quoi je peux m'attendre à l'avenir?

1475voto

Tomalak Points 150423

Vous pouvez utiliser Convert.ToHexString à partir de .NET 5.
Il existe également une méthode pour l'opération inverse : Convert.FromHexString.


Pour les anciennes versions de .NET, vous pouvez utiliser :

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

ou :

public static string ByteArrayToString(byte[] ba)
{
  return BitConverter.ToString(ba).Replace("-","");
}

Il existe encore plus de variantes pour le faire, par exemple ici.

La conversion inverse se ferait comme ceci :

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

Utiliser Substring est la meilleure option en combinaison avec Convert.ToByte. Voir cette réponse pour plus d'informations. Si vous avez besoin de meilleures performances, vous devez éviter Convert.ToByte avant de pouvoir supprimer SubString.

0 votes

Manquant un point-virgule sur la ligne hex.AppendFormat("{0:x2}", b)

26 votes

Vous utilisez SubString. Ce n'est pas ce boucle alloue un horrible montant d'objets de chaîne?

30 votes

Honnêtement - tant que cela ne nuit pas considérablement aux performances, j'aurais tendance à ignorer cela et à faire confiance au runtime et au GC pour s'en occuper.

509voto

patridge Points 12264

Analyse De La Performance

Remarque: le nouveau chef de 2014-07-31.

J'ai couru les différentes méthodes de conversion par le biais de certains brut Stopwatch tests de performance, une course avec une phrase aléatoire (n=61, 1000 itérations) et d'une course avec un Projet de Gutenburg texte (n=1,238,957, 150 itérations). Voici les résultats, à peu près du plus rapide au plus lent. Toutes les mesures sont dans les tiques (de 10 000 ticks = 1 ms) et de toute relative des notes par rapport à l' [lente] StringBuilder mise en œuvre. Pour le code utilisé, voir ci-dessous ou le framework de test repo où j'ai maintenant de maintenir le code pour l'exécution de cette.

Avertissement

AVERTISSEMENT: Ne comptez pas sur ces stats pour quelque chose de concret, ils sont tout simplement un exemple d'exécution de l'échantillon de données. Si vous avez vraiment besoin de haut de gamme de la performance, il est conseillé de tester ces méthodes dans un environnement représentatif de vos besoins de production avec des données représentatives de ce que vous allez utiliser.

Résultats

Les tables de recherche ont pris de l'avance sur l'octet de manipulation. Fondamentalement, il y a une certaine forme de precomputing ce que tout grignoter ou octet sera dans l'hex. Alors, comme vous le déchirer à travers les données, il vous suffit de regarder jusqu'à la prochaine partie pour voir ce que chaîne hexadécimale de il serait. Cette valeur est ensuite ajouté à la chaîne résultante de sortie d'une certaine façon. Pour un long temps d'octets de manipulation, potentiellement plus difficile à lire par certains développeurs, a été le plus performant de l'approche.

Votre meilleur pari est de trouver des données représentatives et de l'essayer dans un environnement de type production. Si vous avez différentes contraintes de mémoire, vous pouvez préférer une méthode avec moins de dotations de l'un qui serait plus rapide, mais qui consomment le plus de mémoire.

Test De Code

N'hésitez pas à jouer avec les tests de code que j'ai utilisé. Une version est inclus ici, mais n'hésitez pas à cloner le repo et ajouter vos propres méthodes. Veuillez soumettre une demande d'extraction si vous trouvez quelque chose d'intéressant ou souhaitez contribuer à améliorer le cadre de tests qu'il utilise.

  1. Ajouter la nouvelle méthode statique (Func<byte[], string>) /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Ajouter que la méthode nom de l' TestCandidates de la valeur de retour dans cette même classe.
  3. Assurez-vous que vous exécutez la version d'entrée que vous voulez, d'une phrase ou d'un texte, en cliquant sur les commentaires en GenerateTestInput dans la même classe.
  4. Frapper la touche F5 et d'attendre la sortie (HTML dump est également généré dans le dossier /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 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();
}

Mise à jour (2010-01-13)

Ajouté Waleed, en réponse à l'analyse. Assez rapide.

Mise à jour (2011-10-05)

Ajouté string.Concat Array.ConvertAll de la variante de l'exhaustivité (nécessite .NET 4.0). Sur le pair avec string.Join version.

Mise à jour (2012-02-05)

Test repo comprend plusieurs variantes telles que StringBuilder.Append(b.ToString("X2")). Aucun bouleverser les résultats. foreach plus rapide que de l' {IEnumerable}.Aggregate, par exemple, mais BitConverter gagne toujours.

Mise à jour (2012-04-03)

Ajouté Mykroft de l' SoapHexBinary réponse à l'analyse, qui a repris la troisième place.

Mise à jour (2013-01-15)

Ajouté CodesInChaos de l'octet de manipulation de réponse, qui a repris la première place (avec une marge sur les gros blocs de texte).

Mise à jour (2013-05-23)

Ajouté Nathan Moinvaziri la recherche de réponse, et la variante de Brian Lambert blog. Les deux plutôt rapide, mais pas prendre la tête de la machine de test, j'ai utilisé (AMD Phenom 9750).

Mise à jour (2014-07-31)

Ajouté @CodesInChaos du nouveau byte-fonction de recherche de réponse. Il semble avoir pris de l'avance sur les deux la phrase tests et le texte intégral des tests.

0 votes

Voudriez-vous tester le code de la réponse de Waleed? Il semble être très rapide. stackoverflow.com/questions/311165/…

6 votes

Malgré avoir mis le code à votre disposition pour que vous puissiez faire exactement ce que vous avez demandé par vous-même, j'ai mis à jour le code de test pour inclure la réponse de Waleed. Mis à part la mauvaise humeur, cela va beaucoup plus vite.

0 votes

J'ai obtenu un résultat différent lorsque j'ai utilisé "ByteArrayToHexStringViaBitConverter" et "ByteArrayToHexStringViaStringBuilder". Ce dernier s'est révélé être "correct". Y a-t-il une raison pour laquelle le résultat des deux fonctions devrait être différent?

250voto

Mykroft Points 4292

Il y a une classe appelée SoapHexBinary qui fait exactement ce que vous voulez.

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

37 votes

SoapHexBinary est disponible à partir de .NET 1.0 et se trouve dans mscorlib. Malgré son drôle d'espace de noms, il fait exactement ce que la question demandait.

4 votes

Super découverte! Notez que vous devrez remplir les chaînes impaires avec un zéro initial pour GetStringToBytes, comme l'autre solution.

0 votes

Avez-vous vu la mise en œuvre réfléchie ? La réponse acceptée en a une meilleure à mon avis.

147voto

CodesInChaos Points 60274

Lors de l'écriture de code crypto, il est courant d'éviter les branches dépendantes des données et les recherches de table pour garantir que le temps d'exécution ne dépend pas des données, car un chronométrage dépendant des données peut entraîner des attaques par canaux secondaires.

C'est aussi assez rapide.

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


Abandonnez tout espoir, vous qui entrez ici

Explication de la manipulation de bits étrange :

  1. bytes[i] >> 4 extrait le nybble élevé d'un octet
    bytes[i] & 0xF extrait le nybble faible d'un octet
  2. b - 10
    est < 0 pour les valeurs b < 10, qui deviendront un chiffre décimal
    est >= 0 pour les valeurs b > 10, qui deviendront une lettre de A à F.
  3. En utilisant i >> 31 sur un entier signé sur 32 bits, on extrait le signe, grâce à l'extension de signe. Ce sera -1 pour i < 0 et 0 pour i >= 0.
  4. En combinant 2) et 3), on voit que (b-10)>>31 sera 0 pour les lettres et -1 pour les chiffres.
  5. En regardant le cas des lettres, le dernier terme de la somme devient 0, et b est dans la plage 10 à 15. Nous voulons le mapper à A (65) à F (70), ce qui implique d'ajouter 55 ('A'-10).
  6. En regardant le cas des chiffres, nous voulons adapter le dernier terme de sorte qu'il mappe b de la plage 0 à 9 dans la plage 0 (48) à 9 (57). Cela signifie qu'il doit devenir -7 ('0' - 55).
    Maintenant, nous pourrions simplement multiplier par 7. Mais comme -1 est représenté par tous les bits étant à 1, nous pouvons plutôt utiliser & -7 puisque (0 & -7) == 0 et (-1 & -7) == -7.

Quelques considérations supplémentaires :

  • Je n'ai pas utilisé de deuxième variable de boucle pour indexer dans c, car les mesures montrent que le calcul à partir de i est moins coûteux.
  • Utiliser exactement i < bytes.Length comme borne supérieure de la boucle permet au JITter d'éliminer les vérifications des limites sur bytes[i], donc j'ai choisi cette variante.
  • Le fait de faire de b un int évite des conversions inutiles de et vers byte.

La même chose peut être implémentée en utilisant la nouvelle fonction string.Create, ce qui évite d'avoir à allouer un tableau char[] séparé.

  • Nous pouvons également factoriser la conversion de chaque nybble dans une fonction.
  • Ajouter AggressiveInlining devrait permettre à cette fonction de disparaître du JIT.
  • Nous pouvons ajuster de 32 pour obtenir un résultat en minuscules.
  • Nous pouvons également utiliser Memory au lieu d'un tableau, cela permet une plus large gamme de tampons mémoire (y compris des tableaux).

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static string ByteToHexBitFiddle(Memory bytes, bool lowercase = false) =>
    lowercase
    ? string.Create(bytes.Length * 2, bytes, LowercaseFillHex)
    string.Create(bytes.Length * 2, bytes, UppercaseFillHex);

    static void UppercaseFillHex(Span span, Memory mem) { var bytes = mem.Span; for (int i = 0; i < bytes.Length; i++) { span[i 2] = ConvertNibble(bytes[i] >> 4, 0); span[i 2 + 1] = ConvertNibble(bytes[i] & 0xF, 0); } }

    static void LowercaseFillHex(Span span, Memory mem) { var bytes = mem.Span; for (int i = 0; i < bytes.Length; i++) { span[i 2] = ConvertNibble(bytes[i] >> 4, 32); span[i 2 + 1] = ConvertNibble(bytes[i] & 0xF, 32); } }

    [MethodImpl(MethodImplOptions.AggressiveInlining)] static char ConvertNibble(int nibble, int adjust) => (char)(55 + adjust + nibble + (((nibble - 10) >> 31) & (-7 - adjust)));

11 votes

Et chaîne hexadécimale en tableau de bytes?

15 votes

+1 pour avoir correctement cité votre source après avoir invoqué ce petit bout de magie noire. Tous saluent Cthulhu.

0 votes

Meilleure réponse (pour la partie codage hexadécimal de la question) !

105voto

Will Dean Points 25866

Si vous souhaitez plus de flexibilité que BitConverter, mais que vous ne voulez pas de ces boucles explicites désuètes des années 1990, alors vous pouvez faire :

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

Ou, si vous utilisez .NET 4.0 :

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

(Ce dernier vient d'un commentaire sur l'article original.)

22 votes

Encore plus court : String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")))

0 votes

Juste une note que la belle technique de maxc nécessite .net 4.0

15 votes

Encore plus court : String.Concat(bytes.Select(b => b.ToString("X2"))) [.NET4]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X