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

1373

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

alextansc
la source
8
La réponse acceptée ci-dessous semble allouer une quantité horrible de chaînes dans la conversion de chaîne en octets. Je me demande comment cela affecte les performances
Wim Coenen
9
La classe SoapHexBinary fait exactement ce que vous voulez, je pense.
Mykroft
Il me semble que poser 2 questions dans 1 poste n'est pas tout à fait standard.
SandRock

Réponses:

1353

Soit:

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 se passerait 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 Substringest 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.ToByteavant de pouvoir abandonner SubString.

Tomalak
la source
24
Vous utilisez SubString. Cette boucle n'alloue-t-elle pas une quantité horrible d'objets chaîne?
Wim Coenen
30
Honnêtement - jusqu'à ce qu'il réduise considérablement les performances, j'aurais tendance à ignorer cela et à faire confiance au Runtime et au GC pour s'en occuper.
Tomalak
87
Étant donné qu'un octet est composé de deux quartets, toute chaîne hexadécimale qui représente valablement un tableau d'octets doit avoir un nombre de caractères pair. Un 0 ne doit être ajouté nulle part - en ajouter un reviendrait à faire l'hypothèse de données non valides potentiellement dangereuses. Si quelque chose, la méthode StringToByteArray doit lever une exception FormatException si la chaîne hexadécimale contient un nombre impair de caractères.
David Boike
7
@ 00jt Vous devez faire l'hypothèse que F == 0F. Soit c'est la même chose que 0F, soit l'entrée a été écrêtée et F est en fait le début de quelque chose que vous n'avez pas reçu. C'est à votre contexte de faire ces hypothèses, mais je crois qu'une fonction à usage général devrait rejeter les caractères impairs comme invalides au lieu de faire cette hypothèse pour le code appelant.
David Boike
11
@DavidBoike La question n'avait RIEN à voir avec "comment gérer les valeurs de flux éventuellement écrêtées". Il s'agit d'une chaîne. String myValue = 10.ToString ("X"); maValeur est "A" et non "0A". Maintenant, allez lire cette chaîne en octets, oups vous l'avez cassée.
00jt
488

Analyse de performance

Remarque: nouveau leader au 20/08/2015.

J'ai exécuté chacune des différentes méthodes de conversion à travers des Stopwatchtests de performances bruts , une analyse avec une phrase aléatoire (n = 61, 1000 itérations) et une analyse avec un texte Project Gutenburg (n = 1238957, 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 à l' StringBuilderimplémentation [la plus lente] . Pour le code utilisé, voir ci-dessous ou le référentiel du framework de test où je maintiens maintenant le code pour l'exécuter.

Avertissement

AVERTISSEMENT: ne vous fiez pas à ces statistiques pour quoi que ce soit de concret; ce sont simplement des échantillons de données. 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 le pas sur la manipulation des octets. Fondamentalement, il existe une certaine forme de précalcul du grignotage ou de l'octet donné en hexadécimal. Ensuite, lorsque vous extrayez les données, vous recherchez simplement la partie suivante pour voir quelle chaîne hexadécimale il s'agirait. Cette valeur est ensuite ajoutée à la sortie de chaîne résultante d'une certaine manière. Pendant longtemps, la manipulation d'octets, potentiellement plus difficile à lire par certains développeurs, a été l'approche la plus performante.

Votre meilleur pari sera toujours de trouver des données représentatives et de les 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 dépôt et à ajouter vos propres méthodes. Veuillez soumettre une demande d'extraction si vous trouvez quelque chose d'intéressant ou souhaitez aider à améliorer le cadre de test qu'il utilise.

  1. Ajoutez la nouvelle méthode statique ( Func<byte[], string>) à /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Ajoutez le nom de cette méthode à la TestCandidatesvaleur de retour 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 GenerateTestInputdans cette même classe.
  4. Appuyez F5et 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 de la réponse de Waleed à l'analyse. Tres rapide.

Mise à jour (2011-10-05)

Ajout d'une string.Concat Array.ConvertAllvariante pour être complet (nécessite .NET 4.0). Au même niveau que la string.Joinversion.

Mise à jour (2012-02-05)

Le repo de test comprend plus de variantes telles que StringBuilder.Append(b.ToString("X2")). Aucun n'a bouleversé les résultats. foreachest plus rapide que {IEnumerable}.Aggregate, par exemple, mais BitConvertergagne toujours.

Mise à jour (2012-04-03)

Ajout de la SoapHexBinaryréponse de Mykroft à l'analyse, qui a pris la troisième place.

Mise à jour (2013-01-15)

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

Mise à jour (2013-05-23)

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

Mise à jour (2014-07-31)

Ajout de la nouvelle réponse de recherche basée sur octets de @ CodesInChaos. Il semble avoir pris la tête des tests de phrases et des tests de texte intégral.

Mise à jour (2015-08-20)

Ajout des optimisations et de la unsafevariante de l' aérographe au dépôt de cette réponse . Si vous voulez jouer dans le jeu dangereux, vous pouvez obtenir d'énormes gains de performance par rapport à l'un des meilleurs gagnants précédents sur les chaînes courtes et les gros textes.

patridge
la source
Souhaitez-vous tester le code de la réponse de Waleed? Cela semble être très rapide. stackoverflow.com/questions/311165/…
Cristian Diaconescu
5
Malgré la mise à disposition du code pour que vous puissiez faire la chose que vous avez demandée par vous-même, j'ai mis à jour le code de test pour inclure la réponse de Waleed. Mis à part tout grincheux, c'est beaucoup plus rapide.
patridge
2
@CodesInChaos Done. Et il a également gagné dans mes tests. Je ne prétends pas encore comprendre pleinement l'une des meilleures méthodes, mais elles sont facilement cachées de l'interaction directe.
patridge
6
Cette réponse n'a pas l'intention de répondre à la question de ce qui est «naturel» ou banal. L'objectif est de donner aux gens des repères de performance de base car, lorsque vous avez besoin de faire ces conversions, vous avez tendance à les faire beaucoup. Si quelqu'un a besoin d'une vitesse brute, il exécute simplement les tests de performances avec des données de test appropriées dans l'environnement informatique souhaité. Ensuite, glissez cette méthode dans une méthode d'extension où vous ne regarderez plus jamais son implémentation (par exemple, bytes.ToHexStringAtLudicrousSpeed()).
patridge
2
Je viens de produire une implémentation basée sur une table de recherche haute performance. Sa variante sûre est environ 30% plus rapide que le leader actuel sur mon CPU. Les variantes dangereuses sont encore plus rapides. stackoverflow.com/a/24343727/445517
CodesInChaos
244

Il existe 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();
}
Mykroft
la source
35
SoapHexBinary est disponible à partir de .NET 1.0 et se trouve dans mscorlib. Malgré son espace de noms drôle, il fait exactement ce que la question a posé.
Sly Gryphon
4
Super trouvaille! Notez que vous devrez remplir les chaînes impaires avec un 0 de début pour GetStringToBytes, comme l'autre solution.
Carter Medlin
Avez-vous vu la pensée de la mise en œuvre? La réponse acceptée a une meilleure IMHO.
mfloryan
6
Intéressant de voir l'implémentation Mono ici: github.com/mono/mono/blob/master/mcs/class/corlib/…
Jeremy
1
SoapHexBinary n'est pas pris en charge dans .NET Core / .NET Standard ...
juFo
141

Lors de l'écriture de code cryptographique, il est courant d'éviter les branches dépendantes des données et les recherches de table pour garantir que l'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 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

Une explication du bricolage bizarre:

  1. bytes[i] >> 4extrait le quartet haut d'un octet
    bytes[i] & 0xFextrait le quartet bas d'un octet
  2. b - 10
    est < 0pour les valeurs b < 10, qui deviendra un chiffre décimal
    est >= 0pour les valeurs b > 10, qui deviendra une lettre de Aà F.
  3. L'utilisation i >> 31sur un entier signé de 32 bits extrait le signe, grâce à l'extension de signe. Ce sera -1pour i < 0et 0pour i >= 0.
  4. La combinaison de 2) et 3) montre que ce (b-10)>>31sera 0pour les lettres et -1pour les chiffres.
  5. En examinant la casse des lettres, la dernière sommation devient 0et se bsitue dans la plage de 10 à 15. Nous voulons la mapper sur A(65) à F(70), ce qui implique d'ajouter 55 ( 'A'-10).
  6. En examinant le cas des chiffres, nous voulons adapter le dernier sommet afin qu'il mappe bde la plage 0 à 9 à la plage 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 & -7depuis (0 & -7) == 0et (-1 & -7) == -7.

Quelques considérations supplémentaires:

  • Je n'ai pas utilisé de deuxième variable de boucle pour l'indexation c, car la mesure montre que son calcul iest moins cher.
  • Utiliser exactement i < bytes.Lengthla limite supérieure de la boucle permet au JITter d'éliminer les vérifications de limites bytes[i], j'ai donc choisi cette variante.
  • Faire bun int permet des conversions inutiles de et vers octet.
CodesInChaos
la source
10
Et hex stringà byte[] array?
AaA
15
+1 pour avoir correctement cité votre source après avoir invoqué ce morceau de magie noire. Je salue tous Cthulhu.
Edward
4
Qu'en est-il de la chaîne à octet []?
Syaiful Nizam Yahya
9
Agréable! Pour ceux qui ont besoin d'une sortie en minuscules, l'expression change évidemment en87 + b + (((b-10)>>31)&-39)
eXavier
2
@AaA Vous avez dit " byte[] array", ce qui signifie littéralement un tableau de tableaux d'octets, ou byte[][]. Je me moquais juste.
CoolOppo
97

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

Will Dean
la source
21
Encore plus court: String.Concat (Array.ConvertAll (octets, x => x.ToString ("X2"))
Nestor
14
Encore plus court: String.Concat (bytes.Select (b => b.ToString ("X2"))) [.NET4]
Allon Guralnek
14
Ne répond qu'à la moitié de la question.
Sly Gryphon
1
Pourquoi le second a-t-il besoin de .Net 4? String.Concat est dans .Net 2.0.
Polyfun
2
ces boucles de «style des années 90» sont généralement plus rapides, mais d'une quantité suffisamment négligeable pour que cela n'ait pas d'importance dans la plupart des contextes. Encore à mentionner
Austin_Anderson
69

Une autre approche basée sur une table de recherche. Celui-ci utilise une seule 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 aussi testé des variantes de ce en utilisant ushort, struct{char X1, X2},struct{byte X1, X2} dans la table de consultation.

Selon la cible de compilation (x86, X64), ceux-ci avaient à peu près les mêmes performances ou étaient légèrement plus lents que cette variante.


Et pour des performances encore plus élevées, son unsafefrè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;
}
CodesInChaos
la source
Pourquoi la création de la table de recherche dans la version non sécurisée permute-t-elle les quartets de l'octet précalculé? Je pensais que l'endianité ne changeait que l'ordre des entités formées de plusieurs octets.
Raif Atef
@RaifAtef Ce qui importe ici n'est pas l'ordre des grignotages. Mais l'ordre des mots de 16 bits dans un entier de 32 bits. Mais j'envisage de le réécrire pour que le même code puisse s'exécuter indépendamment de l'endianité.
CodesInChaos
En relisant le code, je pense que vous l'avez fait parce que lorsque vous convertissez le caractère * plus tard en uint * et l'assignez (lors de la génération du caractère hexadécimal), le runtime / CPU inversera les octets (puisque uint n'est pas traité par le comme 2 caractères 16 bits distincts), vous devez donc les pré-retourner pour compenser. Ai-je raison ? L'endianisme est déroutant :-).
Raif Atef
4
Cela répond juste à la moitié de la question ... Que diriez-vous de la chaîne hexadécimale en octets?
Narvalex
3
@CodesInChaos Je me demande si Spanpeut être utilisé maintenant au lieu de unsafe??
Konrad
64

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

Production:

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

Pour plus d'informations: Méthode BitConverter.ToString (octet [])

Baget
la source
14
Ne répond qu'à la moitié de la question.
Sly Gryphon
3
Où est la deuxième partie de la réponse?
Sawan
56

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 [] Array to Hex String (voir l'article de PZahra). J'ai un peu modifié le code pour supprimer le préfixe 0x.

J'ai fait des tests de performances sur le code et c'était presque huit fois plus rapide que d'utiliser BitConverter.ToString () (le plus rapide selon le post de patridge).

Waleed Eissa
la source
sans compter que cela utilise le moins de mémoire. Aucune chaîne intermédiaire créée que ce soit.
Chochos
8
Ne répond qu'à la moitié de la question.
Sly Gryphon
C'est génial car cela fonctionne sur pratiquement toutes les versions de NET, y compris NETMF. Un gagnant!
Jonesome Reinstate Monica
1
La réponse acceptée fournit 2 excellentes méthodes HexToByteArray, qui représentent l'autre moitié de la question. La solution de Waleed répond à la question de savoir comment procéder sans créer un grand nombre de chaînes dans le processus.
Brendten Eickstaedt
Est-ce que la nouvelle chaîne (c) copie et ré-alloue ou est-elle suffisamment intelligente pour savoir quand elle peut simplement encapsuler le caractère []?
jjxtra
19

Il s'agit d'une réponse à la révision 4 de la réponse très populaire de Tomalak (et des modifications ultérieures).

Je ferai valoir que cette modification est erronée et expliquerai pourquoi elle pourrait être annulée. En cours de route, vous pourriez apprendre une ou deux choses sur certains éléments internes et voir encore un autre exemple de ce qu'est vraiment l'optimisation prématurée et comment elle peut vous mordre.

tl; dr: utilisez simplement Convert.ToByteet String.Substringsi 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 les autres réponses) qui ne sert pas Convert.ToBytesi vous avez besoin de performances. Ne pas utiliser autre chose que , String.Substringen combinaison avec Convert.ToByte, à moins que 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 une Convert.ToByte(char[], Int32)surcharge est implémentée dans le framework. Il est peu probable que cela se produise bientôt.

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

Code d'origine:

    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.Substringet utilise un à la StringReaderplace. La raison donnée est:

Edit: vous pouvez améliorer les performances des chaînes longues en utilisant un analyseur en un seul passage, comme ceci:

Eh bien, en regardant le code de référence pourString.Substring , il est déjà clairement "en un seul passage"; et pourquoi ne le serait-il pas? Il fonctionne au niveau de l'octet, pas sur des paires de substitution.

Cependant, il alloue une nouvelle chaîne, mais vous devez Convert.ToBytequand même en allouer une à laquelle passer . De plus, la solution fournie dans la révision alloue encore un autre objet à chaque itération (le tableau à deux caractères); vous pouvez mettre cette allocation en toute sécurité 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 numeralreprésente un seul octet utilisant deux chiffres (symboles).

Mais alors, pourquoi appeler StringReader.Readdeux fois? Appelez simplement sa deuxième surcharge et demandez-lui de lire deux caractères à la fois dans le tableau à deux caractères; et réduisez de deux le nombre d'appels.

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

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

Si vous vous demandez comment Read"lit", regardez simplement le code , il ne fait qu'appeler String.CopyTosur la chaîne d'entrée. Le reste est juste des frais généraux de tenue de livres pour maintenir des valeurs dont nous n'avons pas besoin.

Donc, retirez déjà le lecteur de chaîne et appelez- CopyTovous; 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;
    }

Avez-vous vraiment besoin d'un jindex qui s'incrémente par incréments de deux parallèlement à i? Bien sûr que non, il suffit de multiplier ipar deux (que le compilateur devrait être en mesure d'optimiser pour 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;
    }

À quoi ressemble la solution maintenant? Exactement comme au début, mais au lieu d'utiliser String.Substringpour allouer la chaîne et y copier les données, vous utilisez un tableau intermédiaire dans lequel vous copiez les chiffres hexadécimaux, puis allouez la chaîne vous-même et recopiez les données à partir de le tableau et dans la chaîne (lorsque vous le passez dans le constructeur de chaîne). La deuxième copie peut être optimisée si la chaîne est déjà dans le pool interne, mais String.Substringpourra également l'éviter dans ces cas.

En fait, si vous regardez à String.Substringnouveau, vous voyez qu'il utilise des connaissances internes de bas niveau sur la façon dont les chaînes sont construites pour allouer la chaîne plus rapidement que vous ne le feriez normalement, et il insère le même code utilisé CopyTodirectement par là pour éviter les frais généraux d'appel.

String.Substring

  • Pire: une allocation rapide, une copie rapide.
  • Meilleur cas: pas d'allocation, pas de copie.

Méthode manuelle

  • Pire: deux allocations normales, une copie normale, une copie rapide.
  • Meilleur cas: une allocation normale, une copie normale.

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

Notez que l'utilisation de Convert.ToByteet String.Substringest un choix parfaitement valide si vous n'avez pas besoin de performances extrêmes. N'oubliez pas: n'optez pour une alternative que si vous avez le temps et les ressources pour enquêter sur son bon fonctionnement.

S'il y en avait un Convert.ToByte(char[], Int32), les choses seraient bien sûr différentes (il serait possible de faire ce que je viens de décrire et d'éviter complètement String).

Je soupçonne que les personnes qui signalent de meilleures performances en «évitant String.Substring» évitent également Convert.ToByte(String, Int32), ce 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écompilé la dernière version du framework pour vérifier que la source de référence est à jour, je suppose que oui.

Maintenant, tout cela semble bien et logique, j'espère même évident si vous avez réussi à aller aussi loin. Mais est-ce 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!

Props à Partridge pour le cadre de banc, il est facile de pirater. L'entrée utilisée est le hachage SHA-1 suivant répété 5000 fois pour créer une chaîne longue de 100 000 octets.

209113288F93A9AB8E474EA78D899AFDBB874355

S'amuser! (Mais optimisez avec modération.)

tne
la source
erreur: {"Impossible de trouver des chiffres reconnaissables."}
Priya Jagtap
17

Complément pour répondre 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 prendre en charge é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; c'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 'a' .. 'f' nous devons faire de grands nombres, nous devons donc 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 la table ASCII ( ...456789:;<=>?@ABCD...).

CoperNick
la source
16

Ce problème peut également être résolu à l'aide d'une table de correspondance. Cela nécessiterait une petite quantité de mémoire statique pour l'encodeur et le décodeur. Cette méthode sera cependant rapide:

  • Table du codeur 512 octets ou 1024 octets (deux fois la taille si les majuscules et les minuscules sont nécessaires)
  • Table du décodeur 256 octets ou 64 Ko (soit une recherche à un seul caractère soit une recherche à deux caractères)

Ma solution utilise 1024 octets pour la table de codage 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]));
}

Codage

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

Remarque

Pendant le décodage, IOException et IndexOutOfRangeException peuvent se produire (si un caractère a une valeur trop élevée> 256). Des méthodes de dé / codage des flux ou des tableaux doivent être implémentées, ce n'est qu'une preuve de concept.

gelé
la source
2
L'utilisation de la mémoire de 256 octets est négligeable lorsque vous exécutez du code sur le CLR.
dolmen
9

Ceci est un excellent article. 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 également besoin du processus inverse, convertissant une chaîne hexadécimale en un tableau d'octets, donc je l'ai écrit comme une inversion 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 exécuté le processus inverse via 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;
}
Chris F
la source
Ce code suppose que la chaîne hexadécimale utilise des caractères alpha majuscules et explose si la chaîne hexadécimale utilise l'alpha minuscule. Pourrait être sûr de vouloir faire une conversion "majuscule" sur la chaîne d'entrée.
Marc Novakowski
Voilà une observation astucieuse Marc. Le code a été écrit pour inverser la solution de Waleed. L'appel ToUpper ralentirait l'algorithme, mais lui permettrait de gérer les caractères alpha en minuscules.
Chris F
3
Convert.ToByte (topChar + bottomChar) peut être écrit comme (octet) (topChar + bottomChar)
Amir Rezaei
Pour gérer les deux cas sans pénalité de performance importante,hexString[i] &= ~0x20;
Ben Voigt
9

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("-", "")
Craig Poulton
la source
2
la raison en est la performance, lorsque vous avez besoin d'une solution haute performance. :)
Ricky
7

Ne pas empiler les nombreuses réponses ici, mais j'ai trouvé une implémentation assez optimale (~ 4,5x meilleure qu'acceptée), simple de l'analyseur de chaîne hexadécimal. 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 oeuvre:

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é quelques trucs avec unsafeet déplacé la ifséquence caractère à grignoter (clairement redondante) 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 chaîne -> octet [] était sous-représentée, tandis que l'angle octet [] -> chaîne semble être bien couvert. Ainsi, cette réponse.)

Ben Mosher
la source
1
Pour les adeptes de Knuth: je l'ai fait parce que j'ai besoin d'analyser quelques milliers de chaînes hexadécimales toutes les quelques minutes environ, il est donc important que ce soit aussi rapide que possible (dans la boucle interne, pour ainsi dire). La solution de Tomalak n'est pas notablement plus lente si de nombreuses analyses de ce type ne se produisent pas.
Ben Mosher
5

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

Versions dangereuses Pour ceux qui préfèrent les performances et n'ont pas peur de l'insécurité. ToHex environ 35% plus rapide et FromHex 10% plus rapide.

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 tester les tests d'initialisation de l'alphabet à chaque fois que la fonction de conversion appelée est incorrecte, l'alphabet doit être const (pour la chaîne) ou statique en lecture seule (pour char []). La conversion alphabétique de l'octet [] en chaîne devient alors aussi rapide que les versions de manipulation d'octets.

Et bien sûr, le test doit être compilé dans la version (avec optimisation) et avec l'option de débogage "Supprimer l'optimisation JIT" désactivée (idem pour "Activer Just My Code" si le code doit être débogable).

Maratius
la source
5

Fonction inverse pour le code Waleed Eissa (Hex String To Byte Array):

    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 Eissa Waleed avec prise en charge des 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);
    }
Géographie
la source
4

Méthodes d'extension (avertissement: code complètement 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).

Pure.Krome
la source
Vous devriez probablement tester le code avant de le proposer pour une question comme celle-ci.
2017
3

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

Bien que ce qui précède soit propre et compact, les accros à la performance en crieront à l'aide d'énumérateurs. Vous pouvez obtenir des performances optimales avec une version améliorée de la réponse originale de Tomalak :

public static string ByteArrayToString(byte[] ba)   
{   
   StringBuilder hex = new StringBuilder(ba.Length * 2);   

   for(int i=0; i < ba.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 la plus rapide de toutes les routines que j'ai vues jusqu'à présent. Ne vous fiez pas seulement à ma parole ... testez les performances de chaque routine et inspectez vous-même son code CIL.

Mark
la source
2
L'itérateur n'est pas le principal problème de ce code. Vous devriez comparer b.ToSting("X2").
dolmen
2

Et pour l'insertion 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("-", "");
}
Jack Straw
la source
si Source == nullou Source.Length == 0nous avons un problème monsieur!
Andrei Krasutski
2

En termes de vitesse, cela semble être mieux que tout 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);
  }
Alexey Borzenkov
la source
2

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

J'ai cependant eu un certain succès en prenant quelques indices du code Waleeds et en martelant cela ensemble. C'est moche comme l'enfer mais il semble fonctionner et performer à 1/3 du temps par rapport aux autres selon mes tests (en utilisant le mécanisme de test des ponts). Selon la taille d'entrée. Changer le?: S pour séparer d'abord 0-9 donnerait probablement un résultat légèrement plus rapide car il y a plus de chiffres 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;
}
Fredrik Hu
la source
2

Cette version de ByteArrayToHexViaByteManipulation pourrait être plus rapide.

De mes rapports:

  • ByteArrayToHexViaByteManipulation3: 1,68 ticks en moyenne (sur 1000 pistes), 17,5X
  • ByteArrayToHexViaByteManipulation2: 1,73 ticks en moyenne (sur 1000 pistes), 16,9X
  • ByteArrayToHexViaByteManipulation: 2,90 ticks moyens (sur 1000 pistes), 10,1X
  • ByteArrayToHexViaLookupAndShift: 3,22 ticks moyens (sur 1000 runs), 9,1X
  • ...

    static private readonly char[] hexAlphabet = new char[]
        {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    static string ByteArrayToHexViaByteManipulation3(byte[] bytes)
    {
        char[] c = new char[bytes.Length * 2];
        byte b;
        for (int i = 0; i < bytes.Length; i++)
        {
            b = ((byte)(bytes[i] >> 4));
            c[i * 2] = hexAlphabet[b];
            b = ((byte)(bytes[i] & 0xF));
            c[i * 2 + 1] = hexAlphabet[b];
        }
        return new string(c);
    }

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);
    }
JoseH
la source
2

Je vais entrer dans cette compétition de bidouilles de bits car j'ai une réponse qui utilise également des bidouilles de bits pour décoder les hexadécimaux. Notez que l'utilisation de tableaux de caractères peut être encore plus rapide car les StringBuilderméthodes d' appel prendront également 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.

Maarten Bodewes
la source
Hmm, je devrais vraiment l'optimiser Char[]et l'utiliser en Charinterne au lieu des pouces ...
Maarten Bodewes
Pour C #, l'initialisation des variables où elles sont utilisées, au lieu de sortir de la boucle, est probablement préférable pour permettre au compilateur d'optimiser. J'obtiens des performances équivalentes de toute façon.
Peteter
2

Pour les performances, j'irais avec la solution drphrozens. Une minuscule optimisation pour le décodeur pourrait être d'utiliser une table pour chaque caractère pour se débarrasser du "<< 4".

De toute évidence, les deux appels de méthode sont coûteux. Si une sorte de vérification est effectuée sur les données d'entrée ou de sortie (peut être CRC, somme de contrôle ou autre), cela if (b == 255)...peut être ignoré et, par conséquent, la méthode appelle également.

Utiliser offset++et offsetau lieu de offsetet offset + 1pourrait donner des avantages théoriques, mais je soupçonne 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 comparé.

ClausAndersen
la source
1

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;          
}
Stas Makutin
la source
1

Pas 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
MCattle
la source
1

Deux mashups qui plient 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 );
}

Version décadente linq-with-bit-hacking:

public static string ByteArrayToString(byte[] ba)
{
    return string.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) );
}

Et inverser:

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;
}
JJJ
la source
1
HexStringToByteArray ("09") renvoie 0x02, ce qui est mauvais
CoperNick
1

Une autre façon consiste stackallocà 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 ] = '\0';
        return new string(c);
}
Kel
la source
1

Voici ma chance. J'ai créé une paire de classes d'extension pour étendre la chaîne et l'octet. Sur le test des fichiers volumineux, les performances sont comparables à la manipulation d'octets 2.

Le code ci-dessous pour ToHexString est une implémentation optimisée de l'algorithme de recherche et de décalage. Il est presque identique à celui de Behrooz, mais il s'avère utiliser un foreachpour itérer et un compteur est plus rapide qu'une indexation explicitefor .

Il vient en 2e position derrière Byte Manipulation 2 sur ma machine et est un code très lisible. Les résultats des tests suivants sont également intéressants:

ToHexStringCharArrayWithCharArrayLookup: 41 589,69 ticks moyens (plus de 1000 runs), 1,5X ToHexStringCharArrayWithStringLookup: 50 764,06 ticks moyens (plus de 1000 runs), 1,2X ToHexStringStringBuilderWithCharArrayLookup: 62 812,87 plus

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

  1. Les pénalités pour l'indexation dans une chaîne pour effectuer la recherche par rapport à un tableau de caractères sont importantes dans le test de fichiers volumineux.
  2. Les pénalités pour l'utilisation d'un StringBuilder de capacité connue par rapport à un tableau de caractères de taille connue pour créer la chaîne sont encore plus importantes.

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

Voici les résultats des tests que j'ai obtenus lorsque j'ai mis mon code dans le projet de test de @ patridge sur ma machine. J'ai également ajouté un test pour convertir un tableau d'octets en hexadécimal. Les tests qui ont exercé mon code sont ByteArrayToHexViaOptimizedLookupAndShift et HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte a été extrait de XXXX. Le 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/>

Conversion d'un tableau d'octets en une représentation sous forme de chaîne hexadécimale


ByteArrayToHexViaByteManipulation2: 39366,64 ticks moyens (sur 1000 pistes), 22,4X

ByteArrayToHexViaOptimizedLookupAndShift: 41 588,64 ticks en moyenne (sur 1 000 exécutions), 21,2 fois

ByteArrayToHexViaLookup: 55 509,56 ticks en moyenne (sur 1000 pistes), 15,9 fois

ByteArrayToHexViaByteManipulation: 65 349,12 ticks moyens (sur 1000 pistes), 13,5 fois

ByteArrayToHexViaLookupAndShift: 86 926,87 ticks moyens (plus de 1 000 exécutions), 10,2 X

ByteArrayToHexStringViaBitConverter: 139353,73 ticks moyens (sur 1000 pistes), 6,3X

ByteArrayToHexViaSoapHexBinary: 314 598,77 ticks moyens (plus de 1 000 exécutions), 2,8 fois

ByteArrayToHexStringViaStringBuilderForEachByteToString: 344 264,63 ticks moyens (plus de 1000 exécutions), 2,6 X

ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382 623,44 ticks moyens (plus de 1 000 exécutions), 2,3 X

ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818 111,95 ticks en moyenne (sur 1000 exécutions), 1,1X

ByteArrayToHexStringViaStringConcatArrayConvertAll: 839 244,84 ticks en moyenne (sur 1000 pistes), 1,1X

ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867 303,98 ticks moyens (plus de 1000 exécutions), 1,0X

ByteArrayToHexStringViaStringJoinArrayConvertAll: 882 710,28 ticks moyens (plus de 1 000 exécutions), 1,0 X


JamieSee
la source
1

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;
}
spacepille
la source