Question Comment convertir un tableau d'octets en une chaîne hexadécimale, et vice versa?


Comment pouvez-vous convertir un tableau d'octets en une chaîne hexadécimale, et vice versa?


1115
2018-03-08 21:56


origine


Réponses:


Non plus:

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 y a encore plus de variantes pour le faire, par exemple ici.

La conversion inverse irait 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;
}

En utilisant 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 tomber SubString.


1071



Analyse de performance

Note: nouveau leader au 2015-08-20.

J'ai couru chacune des différentes méthodes de conversion à travers un peu de brut Stopwatch test de performance, une exécution avec une phrase aléatoire (n = 61, 1000 itérations) et une exécution avec un texte Project Gutenburg (n = 1 238 957, 150 itérations). Voici les résultats, du plus rapide au plus lent. Toutes les mesures sont en ticks (10 000 ticks = 1 ms) et toutes les notes relatives sont comparées au [plus lent] StringBuilder la mise en oeuvre. Pour le code utilisé, voir ci-dessous ou le cadre de test repo où je maintiens maintenant le code pour exécuter ceci.

Avertissement

AVERTISSEMENT: Ne comptez pas sur ces statistiques pour quelque chose de concret; ils sont simplement un échantillon de données d'échantillon. Si vous avez vraiment besoin de performances de premier ordre, veuillez tester ces méthodes dans un environnement représentatif de vos besoins de production avec des données représentatives de ce que vous utiliserez.

Résultats

Les tables de recherche ont pris l'avantage sur la manipulation des octets. Fondamentalement, il y a une certaine forme de précomputing ce que n'importe quel grignotage ou byte donné sera en hex. Ensuite, au fur et à mesure que vous déchirez les données, vous cherchez simplement la chaîne hexadécimale suivante. Cette valeur est ensuite ajoutée à la sortie de chaîne résultante d'une certaine manière. Pendant longtemps, la manipulation des octets, potentiellement plus difficile à lire par certains développeurs, a été l'approche la plus performante.

Votre meilleur pari va toujours être de trouver des données représentatives et de l'essayer dans un environnement de production. Si vous avez des contraintes de mémoire différentes, vous pouvez préférer une méthode avec moins d'allocations à une qui serait plus rapide mais consommerait plus de mémoire.

Code de test

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

  1. Ajouter la nouvelle méthode statique (Func<byte[], string>) à /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Ajoutez le nom de cette méthode au TestCandidates renvoie la valeur dans cette même classe.
  3. Assurez-vous que vous exécutez la version d'entrée souhaitée, phrase ou texte, en basculant les commentaires dans GenerateTestInput dans cette même classe.
  4. Frappé F5 et attendez la sortie (un vidage HTML 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 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();
}

Mise à jour (2010-01-13)

Ajouté la réponse de Waleed à l'analyse. Tres rapide.

Mise à jour (2011-10-05)

Ajoutée string.Concat  Array.ConvertAll variante pour l'exhaustivité (requiert .NET 4.0). À égalité avec string.Join version.

Mise à jour (2012-02-05)

Test repo comprend plus de variantes telles que StringBuilder.Append(b.ToString("X2")). Aucun n'a bouleversé les résultats. foreach est plus rapide que {IEnumerable}.Aggregatepar exemple, mais BitConverter gagne toujours.

Mise à jour (2012-04-03)

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

Mise à jour (2013-01-15)

Ajout de la réponse de manipulation des octets de CodesInChaos, qui a pris la première place (avec une grande marge sur les gros blocs de texte).

Mise à jour (2013-05-23)

Ajouté la réponse de recherche de Nathan Moinvaziri et la variante du blog de Brian Lambert. Les deux plutôt rapides, mais ne prenant pas les devants sur la machine d'essai que j'ai utilisée (AMD Phenom 9750).

Mise à jour (2014-07-31)

Ajout de la nouvelle réponse de recherche basée sur les octets de @ CodesInChaos. Il semble avoir pris les devants à la fois sur les tests de la phrase et les tests de texte intégral.

Mise à jour (2015-08-20)

Ajoutée Airbreather's optimisations et unsafe variante à cette repo de la réponse. Si vous voulez jouer dans le jeu dangereux, vous pouvez obtenir des gains de performance énormes sur l'un des meilleurs gagnants précédents à la fois sur les chaînes courtes et les grands textes.


405



Il y a une classe appelée SoapHexBinary Cela 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();
}

210



Lors de l'écriture d'un code crypto, il est courant d'éviter les branches dépendant des données et les recherches de table pour s'assurer que le temps d'exécution ne dépend pas des données, car le timing dépendant des données peut conduire à des attaques par canal latéral.

C'est aussi très 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

Une explication de l'étrange tripotage:

  1. bytes[i] >> 4 extrait le grignotage élevé d'un octet
    bytes[i] & 0xF extrait le petit quartet d'un octet
  2. b - 10
    est < 0 pour les valeurs b < 10, qui deviendra un chiffre décimal
    est >= 0 pour les valeurs b > 10, qui deviendra une lettre de A à F.
  3. En utilisant i >> 31 sur un entier signé 32 bits extrait le signe, grâce à l'extension de signe. Ce sera -1 pour i < 0 et 0 pour i >= 0.
  4. Combinant 2) et 3), montre que (b-10)>>31 sera 0 pour les lettres et -1 pour les chiffres.
  5. En regardant l'affaire des lettres, le dernier summand devient 0, et b est compris entre 10 et 15. Nous souhaitons A(65) à F(70), ce qui implique d'ajouter 55 ('A'-10).
  6. En regardant le cas pour les chiffres, nous voulons adapter le dernier summand afin qu'il cartographie b de la gamme 0 à 9 à la gamme 0(48) à 9(57). Cela signifie qu'il doit devenir -7 ('0' - 55).
    Maintenant, nous pourrions simplement multiplier par 7. Mais puisque -1 est représenté par tous les bits étant 1, nous pouvons utiliser à la place & -7 depuis (0 & -7) == 0 et (-1 & -7) == -7.

Quelques considérations supplémentaires:

  • Je n'ai pas utilisé une deuxième variable de boucle pour indexer c, puisque la mesure montre que le calcul de i est moins cher.
  • En utilisant exactement i < bytes.Length comme limite supérieure de la boucle permet à la JITter d'éliminer les contrôles de limites sur bytes[i], donc j'ai choisi cette variante.
  • Fabrication b un int permet des conversions inutiles de et vers l'octet.

123



Si vous voulez plus de flexibilité que BitConverter, mais ne veulent pas ces boucles explicites de style 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 à partir d'un commentaire sur le post original.)


83



Vous pouvez utiliser la méthode BitConverter.ToString:

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

Sortie:

00-01-02-04-08-10-20-40-80-FF

Plus d'information: BitConverter.ToString, méthode (octet [])


56



Une autre approche basée sur une table de recherche. Celui-ci utilise seulement une table de recherche pour chaque octet, au lieu d'une table de recherche par quartet.

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

J'ai également testé des variantes de ce à l'aide ushort, struct{char X1, X2}, struct{byte X1, X2} dans la table de recherche.

Selon la cible de compilation (x86, X64), ceux-ci avaient approximativement la même performance ou étaient légèrement plus lents que cette variante.


Et pour encore plus de performance, son unsafe enfant de mêmes parents:

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

Ou si vous considérez acceptable d'écrire directement dans la chaîne:

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

53



Je viens de rencontrer le même problème aujourd'hui, et je suis tombé sur ce code:

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

Source: Message du forum octet [] Array to Hex String (voir le post par PZahra). J'ai légèrement modifié le code pour supprimer le préfixe 0x.

J'ai fait quelques tests de performance sur le code et c'était presque huit fois plus rapide que d'utiliser BitConverter.ToString () (le plus rapide selon le message de Patrick).


52