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

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

1145
demandé sur alextansc 2008-11-22 13:03:13

30 réponses

:

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

L'utilisation de Substring est la meilleure option en combinaison avec Convert.ToByte . Voir cette réponse pour plus d'informations. Si vous avez besoin de plus performance, vous devez éviter Convert.ToByte avant de pouvoir laisser tomber SubString .

1113
répondu Tomalak 2018-09-27 09:23:46

Analyse De Performance

Note: nouveau chef à partir de 2015-08-20.

j'ai exécuté chacune des différentes méthodes de conversion à travers quelques tests de performance rudimentaires Stopwatch , une exécution avec une phrase aléatoire (n=61, 1000 itérations) et une exécution avec un texte de projet Gutenburg (n=1 238 957, 150 itérations). Voici les résultats, en gros du plus rapide au plus lent. Toutes les mesures sont en tiques ( 10 000 tiques = 1 ms ) et toutes les notes relatives sont comparées à l'implémentation [la plus lente] StringBuilder . Pour le code utilisé, voir ci-dessous ou le test framework repo où je maintiens le code pour l'exécuter.

Avertissement

AVERTISSEMENT: Ne vous fiez pas à ces statistiques pour quoi que ce soit de concret; il s'agit simplement d'une série d'échantillons de données. Si vous avez vraiment besoin de performances de premier ordre, s'il Vous Plaît 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 la tête de la manipulation des octets. Fondamentalement, il y a une certaine forme de pré-calcul de ce que tout nibble ou octet donné sera dans hex. Puis, comme vous déchirez les données, vous regardez simplement la partie suivante pour voir quelle chaîne de hexagones ce serait. Cette valeur est ensuite ajouté à la chaîne résultante de sortie d'une certaine façon. Pendant longtemps la manipulation byte, potentiellement plus difficile à lire par certains développeurs, a été l'approche la plus performante.

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

Code D'Essai

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 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 statique méthode ( Func<byte[], string> ) to /Tests/ConvertByteArrayToHexString/Test.cs.
  2. ajouter le nom de cette méthode à la valeur de retour TestCandidates dans cette même catégorie.
  3. assurez-vous que vous exécutez la version d'entrée que vous voulez, phrase ou texte, en activant les commentaires dans GenerateTestInput dans cette même classe.
  4. Hit 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 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)

ajoute la réponse de Waleed à l'analyse. Assez rapide.

mise à jour (2011-10-05)

a ajouté string.Concat Array.ConvertAll variante pour l'exhaustivité (nécessite .NET 4.0). À égalité avec string.Join version.

mise à Jour (2012-02-05)

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

mise à jour (2012-04-03)

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

mise à jour (2013-01-15)

a ajouté la réponse byte manipulation de CodesInChaos, qui a pris la première place (d'une large marge sur les grands blocs de texte).

mise à jour (2013-05-23)

a ajouté la réponse de recherche de Nathan Moinvaziri et la variante du blog de Brian Lambert. Les deux assez rapides, mais ne prenant pas la tête sur la machine d'essai que j'ai utilisé (AMD Phenom 9750).

mise à jour (2014-07-31)

a ajouté la nouvelle réponse de recherche basée sur les octets de @CodesInChaos. Elle semble avoir pris la tête des deux tests, celui des phrases et celui du texte intégral.

mise à jour (2015-08-20)

a ajouté optimisations d'airbreather et unsafe variante à cette repo de answer . Si vous voulez jouer dans le jeu dangereux, vous pouvez obtenir des gains de performance énormes sur l'un des gagnants précédents top sur les chaînes courtes et les grands textes.

415
répondu patridge 2018-07-05 05:15:44

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();
}
212
répondu Mykroft 2018-07-05 04:07:18

lors de l'écriture de code crypto, il est courant d'éviter les branches et les tables de recherche dépendantes des données pour s'assurer que l'exécution ne dépend pas des données, puisque le timing dépendant des données peut conduire à des attaques de 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'Leeh wgah'Nagl fhtagn


abandonnez toute espérance, vous qui entrez ici

, Une explication de l'étrange peu tripoter:

  1. bytes[i] >> 4 extrait le grignotage d'un octet

    bytes[i] & 0xF extrait le grignotage 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 un lettre de A à F .
  3. utilisant i >> 31 sur un entier 32 bits signé extrait le signe, grâce à l'extension du signe. Il 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 la boîte pour les lettres, la dernière invocation devient 0 , et b est dans la gamme de 10 à 15. Nous voulons le faire correspondre à A (65) à F (70), ce qui implique l'ajout de 55 ( 'A'-10 ).
  6. en regardant la case pour les chiffres, nous voulons adapter le dernier summand ainsi il maps b de la gamme 0 à 9 à la gamme 0 (48) à 9 (57). Cela signifie qu'il doit devenir -7 ( '0' - 55 ).

    On pourrait multiplier par 7. Mais puisque -1 est représenté par tous les bits étant 1, Nous pouvons plutôt utiliser & -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 dans c , car la mesure montre que le calcul à partir de i est moins cher.
  • en utilisant exactement i < bytes.Length comme limite supérieure de la boucle permet au JITter d'éliminer les contrôles de limites sur bytes[i] , donc I choisi cette variante.
  • Faire b un int permet inutiles conversions depuis et vers l'octet.
126
répondu CodesInChaos 2016-03-24 14:27:58

si vous voulez plus de flexibilité que BitConverter , mais ne voulez pas ces boucles explicites 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")));

(Le dernier à partir d'un commentaire sur le post original.)

84
répondu Will Dean 2017-06-21 22:56:31

vous pouvez utiliser le BitConverter.Méthode 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'informations: BitConverter.Méthode ToString (Byte)[])

58
répondu Baget 2018-07-05 04:07:56

une Autre table de recherche. Celui-ci utilise une seule table de recherche pour chaque octet, au lieu d'une table de recherche pour grignoter.

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 ushort , struct{char X1, X2} , struct{byte X1, X2} dans la table de recherche.

en fonction de la cible de compilation (x86, X64) ceux-ci ont soit eu la même performance ou étaient légèrement plus lents que cette variante.


et pour des performances encore plus élevées, son unsafe frère:

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 jugez 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;
}
54
répondu CodesInChaos 2014-06-21 17:00:59

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: Forum post byte[] Tableau de Chaîne Hexadécimale (voir le post de PZahra). J'ai un peu 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 patridge du post).

52
répondu Waleed Eissa 2017-06-21 22:58:28

ce problème pourrait également être résolu en utilisant une table de recherche. Cela nécessite une petite quantité de mémoire statique pour le codeur et le décodeur. Cette méthode sera cependant rapide:

  • table D'encodage 512 octets ou 1024 octets (deux fois la taille si les deux cas supérieur et inférieur is neededed)
  • décodeur tableau 256 octets ou 64 KiB (soit une seule recherche ou double char look-up)

ma solution utilise 1024 octets pour la table d'encodage, et 256 octets pour le décodage.

décodage

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

Encodage

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

comparaison

StringBuilderToStringFromBytes:   106148
BitConverterToStringFromBytes:     15783
ArrayConvertAllToStringFromBytes:  54290
ByteManipulationToCharArray:        8444
TableBasedToCharArray:              5651 *

* cette solution

Note

pendant le décodage IOException et Indexoutfrangeexception peuvent se produire (si un caractère a une valeur trop élevée > 256). Les méthodes de suppression/encodage des flux ou des tableaux devraient être mis en œuvre, ce n'est qu'une validation de principe.

15
répondu drphrozen 2017-06-21 23:34:38

C'est une réponse à révision 4 de Tomalak très populaire réponse (et modifications ultérieures).

je vais faire le cas que cette édition est fausse, et expliquer pourquoi il pourrait être inversé. En cours de route, vous pourriez apprendre une chose ou deux au sujet de certains internes, et voir encore un autre exemple de ce que l'optimisation prématurée est vraiment et comment il peut vous mordre.

tl; dr: Il suffit d'utiliser Convert.ToByte et String.Substring si vous êtes pressé ("code Original" ci-dessous), c'est la meilleure combinaison si vous ne voulez pas ré-implémenter Convert.ToByte . Utilisez quelque chose de plus avancé (voir d'autres réponses) qui n'utilise pas Convert.ToByte si vous besoin performance. Faire pas utiliser quelque chose d'autre que String.Substring en combinaison avec Convert.ToByte , sauf si quelqu'un a quelque chose d'intéressant à dire à ce sujet dans les commentaires de cette réponse.

avertissement: Cette réponse peut devenir obsolète si un Convert.ToByte(char[], Int32) surcharge est mis en œuvre dans le cadre. Il est peu probable de se produire bientôt.

en règle générale, je n'aime pas beaucoup à dire "ne pas optimiser prématurément", parce que personne ne sait quand "prématurée". La seule chose que vous devez prendre en considération lorsque vous décidez d'optimiser ou non est: "Ai-je le temps et les ressources pour examiner les approches d'optimisation correctement?". Si vous ne le faites pas, alors c'est trop tôt, attendez jusqu'à ce que votre projet soit plus mature ou jusqu'à ce que vous ayez besoin de la performance (s'il y a un besoin réel, alors vous faire le temps). En attendant, faites la chose la plus simple qui pourrait fonctionner à la place.

code Original:

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

révision 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 révision évite String.Substring et utilise un StringReader à la place. La raison donnée est:

modifier: vous pouvez améliorer les performances pour les chaînes longues en utilisant un seul passer parser, comme ainsi:

Eh bien, en regardant le code de référence pour String.Substring , c'est clairement "single-pass" déjà; et pourquoi ne devrait-il pas être? Il fonctionne au niveau byte, pas sur des paires de substituts.

il attribue un nouveau string cependant, mais ensuite vous devez allouer un passer à Convert.ToByte de toute façon. De plus, la solution fournie dans la révision affecte encore un autre objet à chaque itération (le tableau à deux char); vous pouvez en toute sécurité placer cette allocation en dehors de la boucle et réutiliser le tableau pour éviter cela.

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

chaque hexadécimal numeral représente un seul octet à deux chiffres (symboles).

mais alors, pourquoi appeler StringReader.Read deux fois? Il suffit d'appeler sa deuxième surcharge et de lui demander de lire deux caractères dans le tableau à deux caractères à la fois; et de réduire le nombre d'appels de deux.

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

ce qu'il vous reste est un lecteur de chaînes dont la seule "valeur ajoutée" est un index parallèle (interne _pos ) que vous auriez pu déclarer vous-même (comme j par exemple), une variable de longueur redondante (interne _length ), et une référence redondante à la chaîne de saisie (interne _s ). En d'autres termes, c'est inutile.

si vous vous demandez comment Read " lit", il suffit de regarder le code , tout ce qu'il fait est d'appeler String.CopyTo sur la chaîne de saisie. Le reste, ce n'est que de la comptabilité pour maintenir des valeurs dont nous n'avons pas besoin.

donc, retirez déjà le lecteur de chaîne, et appelez CopyTo vous-même; c'est plus simple, plus clair, et plus efficace.

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

voulez-vous vraiment besoin d'un j index que les incréments dans les étapes de deux parallèles à i ? Bien sûr que non, il suffit de multiplier i par deux (que le compilateur devrait être en mesure d'optimiser à un ajout).

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

A quoi ressemble la solution maintenant? Exactement comme au début, seulement au lieu d'utiliser String.Substring pour allouer la chaîne et copier les données vers elle, vous utilisez un tableau intermédiaire auquel vous copiez les nombres hexadécimaux, puis attribuez la chaîne de caractères vous-même et copiez les données à nouveau à partir du tableau et dans la chaîne de caractères (lorsque vous la passez dans le constructeur de chaînes de caractères). La seconde copie pourrait être optimisée-out si la chaîne est déjà dans le pool interne, mais alors String.Substring pourra également l'éviter dans ces cas.

en fait, si vous regardez à nouveau String.Substring , vous voyez qu'il utilise une connaissance interne de bas niveau de la façon dont les chaînes sont construites pour allouer la chaîne plus vite que vous pourriez normalement le faire, et il inlines le même code utilisé par CopyTo directement dans là pour éviter l'appel au-dessus.

String.Substring

  • Pire-cas: Une allocation rapide, une copie rapide.
  • dans le Meilleur des cas: Pas d'allocation, aucune copie.

méthode manuelle

  • Worst-case: deux attributions normales, une copie normale, une copie rapide.
  • dans le Meilleur des cas: Une répartition normale, une copie normale.

Conclusion? si vous voulez utiliser Convert.ToByte(String, Int32) (parce que vous ne voulez pas ré-implémenter cette fonctionnalité vous-même), il ne semble pas y avoir un moyen de battre String.Substring ; tout ce que vous faites est courir en rond, réinventer la roue (seulement avec des matériaux sous-optimaux).

noter que l'utilisation de Convert.ToByte et String.Substring est un bon choix si vous n'avez pas besoin de performances extrêmes. Rappelez-vous: optez pour une alternative seulement si vous avez le temps et les ressources pour étudier comment cela fonctionne correctement.

S'il y avait un Convert.ToByte(char[], Int32) , les choses seraient différentes bien sûr (il serait possible de faire ce que j'ai décrit ci-dessus et d'éviter complètement String ).

je soupçonne que les gens qui signalent un meilleur rendement en "évitant String.Substring "aussi éviter Convert.ToByte(String, Int32) , que vous devriez vraiment faire si vous avez besoin de la performance de toute façon. Regardez les innombrables autres réponses pour découvrir toutes les différentes approches pour le faire.

avertissement: je n'ai pas décompressé la dernière version du Cadre pour vérifier que la source de référence est à jour, je suppose qu'il est.

maintenant, tout semble bon et logique, avec un peu de chance même évident si vous avez réussi à aller si loin. Mais est-il vrai?

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

Oui!

Accessoires de Perdrix pour le banc cadre, il est facile à pirater. L'entrée utilisée est la suivante SHA-1 hash répété 5000 fois pour faire une longue chaîne de 100.000 bytes.

209113288F93A9AB8E474EA78D899AFDBB874355

amusez-vous bien! (Mais optimiser avec modération.)

14
répondu tne 2018-07-05 04:10:58

complément de réponse par @CodesInChaos (méthode inversée)

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

explication:

& 0x0f est de soutenir également les lettres minuscules

hi = hi + 10 + ((hi >> 31) & 7); est le même que:

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

pour "0"..'9', c'est la même chose que hi = ch - 65 + 10 + 7; , qui est hi = ch - 48 (c'est à cause de 0xffffffff & 7 ).

Pour "A"..'F' c'est hi = ch - 65 + 10; (c'est à cause de 0x00000000 & 7 ).

Pour "une"..'f' Nous avons de grands nombres donc nous devons soustraire 32 de la version par défaut en faisant quelques bits 0 en utilisant & 0x0f .

65 est le code pour 'A'

48 est le code pour '0'

7 est le nombre de lettres entre '9' et 'A' dans le tableau ASCII ( ...456789:;<=>?@ABCD... ).

12
répondu CoperNick 2017-06-21 23:07:43

C'est un grand poste. J'aime la solution de Waleed. Je ne l'ai pas passé à travers le test de patridge mais il semble être assez rapide. J'avais aussi besoin du processus inverse, convertir une chaîne de hexagones en un tableau d'octets, donc je l'ai écrit comme un renversement de la solution de Waleed. Je ne sais pas si C'est plus rapide que la solution originale de Tomalak. Encore une fois, je n'ai pas non plus effectué le processus inverse dans le test de 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;
}
9
répondu Chris F 2010-01-12 16:51:38

pourquoi le rendre complexe? C'est simple Dans Visual Studio 2008:

C#:

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

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
8
répondu Craig Poulton 2017-06-21 23:20:12

pour ne pas empiler sur les nombreuses réponses ici, mais j'ai trouvé une assez optimale (~4.5 x meilleure que acceptée), simple implémentation de l'analyseur de chaîne de hexagones. Tout d'abord, sortie de mes tests (le premier lot est mon implémentation):

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

les lignes base64 et Bitconverter'D sont là pour tester l'exactitude. Notez qu'ils sont égaux.

La mise en œuvre:

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

j'ai essayé des trucs avec unsafe et le déplacement du caractère (manifestement redondant) vers la séquence if vers une autre méthode, mais c'était la plus rapide.

(je concède que cela répond à la moitié de la question. J'ai senti que la conversion string->byte[] était sous-représentée, alors que l'angle byte [] - >string semble être bien couvert. Ainsi, cette réponse.)

7
répondu Ben Mosher 2017-06-21 23:10:33

versions sûres:

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

Dangereux versions Pour ceux qui préfèrent la performance et n'a pas peur de unsafeness. Environ 35% plus rapide ToHex et 10% plus rapide 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 Pour les tests de benchmark initialisant alphabet chaque fois que la fonction de conversion appelée est erronée, l'alphabet doit être const (pour la chaîne) ou statique en lecture seule (pour char[]). Puis la conversion basée sur l'alphabet de byte[] en chaîne devient aussi rapide que les versions de manipulation de byte.

et bien sûr test doivent être compilés dans la version (avec optimisation) et avec l'option de débogage" Supprimer l'optimisation JIT "désactivée (même pour" activer juste mon Code " si le code doit être déboguable).

5
répondu Maratius 2013-12-20 06:33:34

fonction Inverse pour Waleed Eissa code (Hex Chaîne De Tableau d'Octets):

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

        return b;
    }

fonction de Waleed Eissa avec support en minuscules:

    public static string BytesToHex(this byte[] barray, bool toLowerCase = true)
    {
        byte addByte = 0x37;
        if (toLowerCase) addByte = 0x57;
        char[] c = new char[barray.Length * 2];
        byte b;
        for (int i = 0; i < barray.Length; ++i)
        {
            b = ((byte)(barray[i] >> 4));
            c[i * 2] = (char)(b > 9 ? b + addByte : b + 0x30);
            b = ((byte)(barray[i] & 0xF));
            c[i * 2 + 1] = (char)(b > 9 ? b + addByte : b + 0x30);
        }

        return new string(c);
    }
4
répondu Geograph 2015-12-17 11:15:19

les méthodes d'Extension (avertissement: le code non testé, 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();
    }
}

etc.. Utilisez l'une des trois solutions de Tomalak (la dernière étant une méthode d'extension sur une chaîne de caractères).

3
répondu Pure.Krome 2017-06-21 22:51:16

des développeurs de Microsoft, une conversion simple et agréable:

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

alors que le ci-dessus est propre un compact, les accros de la performance vont hurler à ce sujet en utilisant des recenseurs. Vous pouvez obtenir des performances de pointe avec une version améliorée de la réponse originale de 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();   
} 

C'est le plus rapide de tous les routines que j'ai vu posté ici jusqu'à présent. Ne prenez pas mon mot pour lui... tester les performances de chaque routine et inspecter son code CIL m'.

3
répondu Mark 2017-06-21 23:22:00

en termes de vitesse, cela semble être mieux que n'importe quoi ici:

  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);
  }
2
répondu Alexey Borzenkov 2010-06-01 08:19:33

Je n'ai pas eu le code que vous avez suggéré de travailler, Olipro. hex[i] + hex[i+1] apparemment retourné une int .

j'ai, cependant eu un certain succès en prenant quelques conseils du code de Waleeds et en martelant ceci ensemble. C'est laid comme l'enfer mais il semble fonctionner et fonctionne à 1/3 du temps par rapport aux autres selon mes tests (en utilisant le mécanisme de test de patridges). Selon la taille de l'image. De commutation autour de l' ?: s séparer 0-9 d'abord donnerait probablement un résultat un peu plus rapide puisqu'il y a plus de nombres que de lettres.

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;
}
2
répondu Fredrik Hu 2012-01-09 17:29:51

cette version de ByteArrayToHexViaByteManipulation pourrait être plus rapide.

D'après mes rapports:

  • ByteArrayToHexViaByteManipulation3: 1,68 tiques moyennes( plus de 1000 passages), 17,5 X
  • ByteArrayToHexViaByteManipulation2: 1,73 tiques moyennes( plus de 1000 passages), 16,9 X
  • ByteArrayToHexViaByteManipulation: 2,90 tiques moyennes (plus de 1000 passages), 10,1 X
  • Bytearray tohexvial bookup and shift: 3.22 moyenne des tiques (plus de 1000 passages), 9.1 X 151980920"
  • ...

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

et je pense que celui-ci est une optimisation:

    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);
    }
2
répondu JoseH 2013-08-23 07:41:58

je vais entrer dans cette compétition de bit fiddling car j'ai une réponse qui utilise aussi bit-fiddling à decode hexadecimals. Notez que l'utilisation de tableaux de caractères peut être encore plus rapide car l'appel aux méthodes StringBuilder prendra du temps.

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

converti à partir du code Java.

2
répondu Maarten Bodewes 2014-01-20 23:38:28

et pour insérer dans une chaîne SQL (si vous n'utilisez pas de paramètres de commande):

public static String ByteArrayToSQLHexString(byte[] Source)
{
    return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
1
répondu Jack Straw 2009-09-14 21:13:35

encore une autre variation pour 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;          
}
1
répondu Stas Makutin 2011-06-16 20:33:53

non optimisé pour la vitesse, mais plus LINQy que la plupart des réponses (.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
1
répondu MCattle 2013-08-30 23:53:04

deux mashups qui plie les deux opérations de grignotage en une seule.

version probablement assez efficace:

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

Décadent linq-avec-peu-piratage de la version:

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

Et à l'inverse:

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;
}
1
répondu JJJ 2014-07-16 10:53:43

une autre façon consiste à utiliser stackalloc pour réduire la pression de la mémoire 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 ] = '"151900920"';
        return new string(c);
}
1
répondu Kel 2016-05-20 16:23:25

voici ma chance. J'ai créé deux classes d'extension pour étendre string et byte. Sur le test du gros fichier, la performance est comparable à la Manipulation des octets 2.

le code ci-dessous pour ToHexString est une implémentation optimisée de l'algorithme de recherche et de changement. Il est presque identique à celui de Behrooz, mais il s'avère en utilisant un foreach pour itérer et un compteur est plus rapide qu'un indexage explicite for .

It arrive à la deuxième place derrière Byte Manipulation 2 sur ma machine et est très lisible code. Les résultats des essais suivants sont également intéressants:

ToHexStringCharArrayWithCharArraylookup: 41,589.69 tiques moyennes (plus de 1000 passages), 1,5 X Tohexstringchararray withstringlookup: 50,764.06 tiques moyennes (plus de 1000 passages), 1,2 X Tohexstringstringstringbuilderwithchararraylookup: 62,812.87 tiques moyennes (plus de 1000 passages), 1,0 X 151950920"

sur la base des résultats ci-dessus, il semble sûr de conclure que:

  1. Les sanctions pour l'indexation dans une chaîne de caractères pour effectuer la recherche par rapport à un les tableaux de char sont significatifs dans le test de grand fichier.
  2. les pénalités pour l'utilisation d'un StringBuilder de capacité connue par rapport à un char tableau de taille connue pour créer la chaîne sont encore plus importants.

voici le code:

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

ci-dessous sont les résultats des tests que j'ai obtenus quand je mets mon code dans le projet d'essai de @patridge sur ma machine. J'ai aussi ajouté un test pour la conversion en un tableau d'octets de hexadécimal. Les essais qui ont exercé mon code sont ByteArrayToHexViaOptimizedLookupandshift et HexToByteArrayViaByteManipulation. L'HexToByteArrayViaConvertToByte a été prélevé à partir de XXXX. L'HexToByteArrayViaSoapHexBinary est celui de la réponse de @Mykroft.

processeur Intel Pentium III Xeon

    Cores: 4 <br/>
    Current Clock Speed: 1576 <br/>
    Max Clock Speed: 3092 <br/>

la Conversion de tableau d'octets en hexadécimal représentation de chaîne


ByteArrayToHexViaByteManipulation2: 39,366.64 tiques moyennes (plus de 1000 passages), 22,4 X 151950920"

ByteArrayToHexViaOptimizedLookupandshift: 41,588.64 tiques moyennes (plus de 1000 passages), 21.2 x 151950920"

ByteArrayToHexViaLookup: 55,509.56 tiques moyennes( plus de 1000 passages), 15,9 X 151950920"

ByteArrayToHexViaByteManipulation: 65,349.12 tiques moyennes (plus de 1000 passages), 13,5 X 151950920"

ByteArrayToHexViaLookupAndShift: 86,926.87 tiques moyennes (plus de 1000 run), 10.2 x

ByteArrayToHexStringViaBitConverter: 139,353.73 moyenne tiques (plus de 1000 passages),6,3 X 151950920"

ByteArrayToHexViaSoapHexBinary: 314,598.77 tiques moyennes (plus de 1000 passages), 2,8 X 151950920"

Bytearraytohexstring viastringbuilderforeachbytetostring: 344,264.63 moyenne des tiques (plus de 1000 passages), 2,6 X 151950920"

ByteArrayToHexStringViaStringBuilderaggregatebytetostring: 382,623.44 moyenne des tiques (plus de 1000 passages), 2,3 X 151950920"

ByteArrayToHexStringViaStringBuilderforeachappendformat: 818,111.95 moyenne des tiques (plus de 1000 passages), 1,1 X 151950920"

ByteArrayToHexStringViaStringConcatarrayconvertall: 839 244.84 moyenne tique (plus de 1000 passages), 1.1 X

Bytearraytohexstringviastringbuilderagregateappendformat: 867,303.98 moyenne des tiques (plus de 1000 passages), 1,0 X 151950920"

ByteArrayToHexStringViaStringJoinarrayconvertall: 882,710.28 moyenne tiques (plus de 1000 passages), 1,0 X


1
répondu JamieSee 2017-06-21 23:05:14

une autre fonction rapide...

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;
}
1
répondu spacepille 2017-06-21 23:24:36

pour la performance, je choisirais drphrozens solution. Une petite optimisation pour le décodeur pourrait être d'utiliser une table pour deux de char à se débarrasser de la "<< 4".

de toute évidence, les deux appels de méthodes sont coûteux. Si une sorte de contrôle est fait soit sur les données d'entrée ou de sortie (pourrait être CRC, checksum ou quoi que ce soit) le if (b == 255)... pourrait être sauté et donc aussi la méthode appelle tout à fait.

utilisant offset++ et offset au lieu de offset et offset + 1 pourraient donner un certain avantage théorique, mais je pense que le compilateur gère cela mieux que moi.

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

c'est juste au-dessus de ma tête et n'a pas été testé ou référencé.

1
répondu ClausAndersen 2017-06-21 23:33:22