web-dev-qa-db-fra.com

Comment convertir un tableau d'octets en chaîne hexadécimale, et inversement?

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

1275
alextansc

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 pour le faire, par exemple ici .

La conversion inverse irait comme ceci:

public static byte[] StringToByteArray(String hex)
{
  int NumberChars = hex.Length;
  byte[] bytes = new byte[NumberChars / 2];
  for (int i = 0; i < NumberChars; i += 2)
    bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
  return bytes;
}

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

1252
Tomalak

Analyse de performance

Remarque: nouveau chef à compter du 2015-08-20.

J'ai passé en revue chacune des différentes méthodes de conversion par des tests de performances Stopwatch bruts, une exécution avec une phrase aléatoire (n = 61, 1 000 itérations) et une exécution avec un texte de Project Gutenburg (n = 1 238 957, 150 itérations). Voici les résultats, du plus rapide au plus lent. Toutes les mesures sont en graduations ( 10 000 ticks = 1 ms ) et toutes les notes relatives sont comparées à la mise en œuvre [la plus lente] StringBuilder. Pour le code utilisé, voir ci-dessous ou le référentiel de test où je gère maintenant le code pour l’exécuter.

Avertissement

AVERTISSEMENT: ne vous fiez pas à ces statistiques pour des résultats concrets; il s’agit simplement d’une série d’é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 consultation ont pris l’avance sur la manipulation d’octets. Fondamentalement, il existe une forme de précalcul de ce qu'un octet ou un octet donné sera dans l'hex. Ensuite, lorsque vous extrayez les données, il vous suffit de regarder la partie suivante pour voir quelle chaîne hexagonale ce serait. Cette valeur est ensuite ajoutée d'une manière ou d'une autre à la sortie de chaîne résultante. Pendant longtemps, la manipulation des octets, potentiellement plus difficile à lire par certains développeurs, était l'approche la plus performante.

Votre meilleur choix sera toujours de trouver des données représentatives et de les essayer dans un environnement de production. Si vous avez différentes contraintes de mémoire, vous préférerez peut-être une méthode avec moins d'allocations, mais une méthode plus rapide, mais consommant plus de mémoire.

Code de test

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

  1. Ajoutez la nouvelle méthode statique (Func<byte[], string>) à /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Ajoutez le nom de cette méthode à la valeur de retour TestCandidates de la même classe.
  3. Assurez-vous que vous utilisez la version d'entrée souhaitée, phrase ou texte, en basculant les commentaires dans GenerateTestInput dans la même classe.
  4. Frappé F5 et attendez la sortie (un vidage HTML est également généré dans le dossier/bin).
static string ByteArrayToHexStringViaStringJoinArrayConvertAll(byte[] bytes) {
    return string.Join(string.Empty, Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaStringConcatArrayConvertAll(byte[] bytes) {
    return string.Concat(Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaBitConverter(byte[] bytes) {
    string hex = BitConverter.ToString(bytes);
    return hex.Replace("-", "");
}
static string ByteArrayToHexStringViaStringBuilderAggregateByteToString(byte[] bytes) {
    return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.Append(b.ToString("X2"))).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachByteToString(byte[] bytes) {
    StringBuilder hex = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes)
        hex.Append(b.ToString("X2"));
    return hex.ToString();
}
static string ByteArrayToHexStringViaStringBuilderAggregateAppendFormat(byte[] bytes) {
    return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.AppendFormat("{0:X2}", b)).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachAppendFormat(byte[] bytes) {
    StringBuilder hex = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes)
        hex.AppendFormat("{0:X2}", b);
    return hex.ToString();
}
static string ByteArrayToHexViaByteManipulation(byte[] bytes) {
    char[] c = new char[bytes.Length * 2];
    byte b;
    for (int i = 0; i < bytes.Length; i++) {
        b = ((byte)(bytes[i] >> 4));
        c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
        b = ((byte)(bytes[i] & 0xF));
        c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
    }
    return new string(c);
}
static string ByteArrayToHexViaByteManipulation2(byte[] bytes) {
    char[] c = new char[bytes.Length * 2];
    int b;
    for (int i = 0; i < bytes.Length; i++) {
        b = bytes[i] >> 4;
        c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
        b = bytes[i] & 0xF;
        c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
    }
    return new string(c);
}
static string ByteArrayToHexViaSoapHexBinary(byte[] bytes) {
    SoapHexBinary soapHexBinary = new SoapHexBinary(bytes);
    return soapHexBinary.ToString();
}
static string ByteArrayToHexViaLookupAndShift(byte[] bytes) {
    StringBuilder result = new StringBuilder(bytes.Length * 2);
    string hexAlphabet = "0123456789ABCDEF";
    foreach (byte b in bytes) {
        result.Append(hexAlphabet[(int)(b >> 4)]);
        result.Append(hexAlphabet[(int)(b & 0xF)]);
    }
    return result.ToString();
}
static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_Lookup32, GCHandleType.Pinned).AddrOfPinnedObject();
static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes) {
    var lookupP = _lookup32UnsafeP;
    var result = new string((char)0, bytes.Length * 2);
    fixed (byte* bytesP = bytes)
    fixed (char* resultP = result) {
        uint* resultP2 = (uint*)resultP;
        for (int i = 0; i < bytes.Length; i++) {
            resultP2[i] = lookupP[bytesP[i]];
        }
    }
    return result;
}
static uint[] _Lookup32 = Enumerable.Range(0, 255).Select(i => {
    string s = i.ToString("X2");
    return ((uint)s[0]) + ((uint)s[1] << 16);
}).ToArray();
static string ByteArrayToHexViaLookupPerByte(byte[] bytes) {
    var result = new char[bytes.Length * 2];
    for (int i = 0; i < bytes.Length; i++)
    {
        var val = _Lookup32[bytes[i]];
        result[2*i] = (char)val;
        result[2*i + 1] = (char) (val >> 16);
    }
    return new string(result);
}
static string ByteArrayToHexViaLookup(byte[] bytes) {
    string[] hexStringTable = new string[] {
        "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F",
        "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F",
        "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F",
        "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F",
        "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F",
        "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F",
        "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F",
        "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F",
        "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F",
        "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F",
        "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF",
        "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF",
        "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF",
        "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF",
        "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF",
        "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF",
    };
    StringBuilder result = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes) {
        result.Append(hexStringTable[b]);
    }
    return result.ToString();
}

Mise à jour (2010-01-13)

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

Mise à jour (2011-10-05)

Ajout de la variante string.ConcatArray.ConvertAll pour une complétude (nécessite .NET 4.0). À égalité avec string.Join version.

Mise à jour (2012-02-05)

Le référentiel de test inclut plusieurs variantes telles que StringBuilder.Append(b.ToString("X2")). Aucun ne contrarie les résultats. foreach est plus rapide que {IEnumerable}.Aggregate, par exemple, mais BitConverter gagne toujours.

Mise à jour (2012-04-03)

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

Mise à jour (2013-01-15)

Ajout de la réponse de manipulation des octets de CodesInChaos, qui a pris la première place (avec une marge importante sur de grands 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. Les deux sont assez rapides, mais ne prennent 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 des 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 de airbreather et de la variante unsafe à ceci rapport de réponse . Si vous souhaitez jouer dans le jeu dangereux, vous pouvez obtenir d’énormes gains de performances par rapport à l’un des précédents grands gagnants, à la fois avec des chaînes courtes et des textes volumineux.

457
patridge

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

Lors de l'écriture de code crypté, il est courant d'éviter les branches et les recherches de table dépendantes des données afin de garantir que l'exécution ne dépende pas des données, car le minutage dépendant des données peut entraîner 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 de l'étrange peu de tripes:

  1. bytes[i] >> 4 extrait le gros octet d'un octet
    bytes[i] & 0xF extrait le petit octet d'un octet
  2. b - 10
    est < 0 pour les valeurs b < 10, qui deviendront un chiffre décimal
    est >= 0 pour les valeurs b > 10, qui deviendront une lettre de A à F.
  3. Utiliser i >> 31 sur un entier signé de 32 bits extrait le signe, grâce à l’extension de signe. Ce sera -1 pour i < 0 et 0 pour i >= 0.
  4. La combinaison de 2) et 3) indique que (b-10)>>31 sera 0 pour les lettres et -1 pour les chiffres.
  5. En regardant le cas des lettres, le dernier sommet devient 0, et b est compris entre 10 et 15. Nous voulons le mapper sur A (65) à F (70), ce qui implique l'ajout de 55 ('A'-10).
  6. En regardant le cas des chiffres, nous voulons adapter le dernier sommet pour qu'il mappe b de 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 1, tous les bits sont à 1, nous pouvons utiliser plutôt & -7 puisque (0 & -7) == 0 et (-1 & -7) == -7.

Quelques considérations supplémentaires:

  • Je n'ai pas utilisé de seconde variable de boucle pour indexer dans c, car la mesure montre que le calcul de cette valeur à partir de i coûte moins cher.
  • Utiliser exactement i < bytes.Length comme limite supérieure de la boucle permet au JETter d'éliminer les vérifications de limites sur bytes[i], j'ai donc choisi cette variante.
  • Rendre b un entier permet des conversions inutiles de et en octets.
135
CodesInChaos

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

91
Will Dean

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

Selon la cible de la 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, sa unsafe soeur:

private static readonly uint[] _lookup32Unsafe = CreateLookup32Unsafe();
private static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();

private static uint[] CreateLookup32Unsafe()
{
    var result = new uint[256];
    for (int i = 0; i < 256; i++)
    {
        string s=i.ToString("X2");
        if(BitConverter.IsLittleEndian)
            result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
        else
            result[i] = ((uint)s[1]) + ((uint)s[0] << 16);
    }
    return result;
}

public static string ByteArrayToHexViaLookup32Unsafe(byte[] bytes)
{
    var lookupP = _lookup32UnsafeP;
    var result = new char[bytes.Length * 2];
    fixed(byte* bytesP = bytes)
    fixed (char* resultP = result)
    {
        uint* resultP2 = (uint*)resultP;
        for (int i = 0; i < bytes.Length; i++)
        {
            resultP2[i] = lookupP[bytesP[i]];
        }
    }
    return new string(result);
}

Ou si vous considérez qu'il est 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;
}
64
CodesInChaos

Vous pouvez utiliser la méthode BitConverter.ToString:

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

Sortie:

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

Plus d'informations: méthode BitConverter.ToString (Byte [])

62
Baget

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 octet [] Tableau à chaîne hexadécimale (voir l'article de PZahra). J'ai légèrement modifié le code pour supprimer le préfixe 0x.

J'ai fait quelques tests de performance du code et il était presque huit fois plus rapide que d'utiliser BitConverter.ToString () (le plus rapide selon le post de patridge).

53
Waleed Eissa

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

Je ferai valoir que cette vérification est erronée et expliquerai pourquoi elle pourrait être annulée. En cours de route, vous pourrez en apprendre davantage sur certains éléments internes et découvrir un autre exemple de ce qu'est l'optimisation prématurée et de la façon dont elle peut vous mordre.

tl; dr: Utilisez simplement 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 avez besoin de performances. Ne pas utilisez autre chose que String.Substring en combinaison avec Convert.ToByte, sauf si quelqu'un a quelque chose d'intéressant à dire à ce sujet dans le commentaires de cette réponse.

warning: Cette réponse peut devenir obsolète si une surcharge Convert.ToByte(char[], Int32) est implémentée dans le cadre. Il est peu probable que cela se produise bientôt.

En règle générale, je n'aime pas beaucoup dire "n'optimisez pas prématurément", car personne ne sait quand "prématuré" est. La seule chose que vous devez prendre en compte 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 c'est trop tôt, attendez que votre projet soit plus mature ou que vous ayez besoin de la performance (s'il y a un réel besoin, alors vous ferez le temps). En attendant, faites la chose la plus simple qui puisse 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.Substring et utilise à la place un StringReader. 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 pour String.Substring , c'est clairement "passe unique" déjà; et pourquoi ne devrait-il pas en être ainsi? Il fonctionne au niveau octet, pas sur des paires de substitution.

Cependant, il alloue une nouvelle chaîne, mais vous devez en allouer une pour la transmettre à Convert.ToByte de toute façon. De plus, la solution fournie dans la révision alloue encore un autre objet à chaque itération (le tableau à deux caractères); vous pouvez sans risque mettre 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? Appelez simplement sa deuxième surcharge et demandez-lui de lire deux caractères à la fois dans le tableau à deux caractères; et 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 qui vous reste est 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 j, par exemple), une variable de longueur redondante ( internal _length) et une référence redondante à la chaîne d'entrée (internal _s). En d'autres termes, c'est inutile.

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

Supprimez donc déjà le lecteur de chaîne et appelez vous-même CopyTo; 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 index j qui s’incrémente par incréments de deux parallèlement à i? Bien sûr que non, il suffit de multiplier i par deux (que le compilateur devrait pouvoir 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, au lieu d’utiliser String.Substring pour 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 copiez les données à nouveau du tableau et dans la chaîne (lorsque vous les transmettez au constructeur de chaîne). La seconde copie peut être optimisée en sortie 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 relisez String.Substring, vous constatez qu'il utilise des connaissances internes de bas niveau sur la construction des chaînes pour allouer la chaîne plus rapidement que vous ne le pouvez normalement, et insère le même code que celui utilisé par CopyTo directement à l'intérieur pour éviter la surcharge de l'appel.

String.Substring

  • Dans le pire des cas: une attribution rapide, une copie rapide.
  • Meilleur cas: pas d'attribution, pas de copie.

Méthode manuelle

  • Dans le pire des cas: deux affectations normales, une copie normale, une copie rapide.
  • Le meilleur des cas: une allocation normale, une copie normale.

Conclusion? Si vous souhaitez utiliser Convert.ToByte(String, Int32) (car vous ne souhaitez 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 est de tourner en rond en réinventant la roue (uniquement avec des matériaux sous-optimaux).

Notez que l'utilisation de Convert.ToByte et String.Substring est un choix parfaitement valable si vous n'avez pas besoin de performances extrêmes. Rappelez-vous: optez pour une alternative uniquement si vous avez le temps et les ressources nécessaires pour étudier son fonctionnement.

S'il y avait une 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 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 toute façon de la performance. Regardez les innombrables autres réponses pour découvrir toutes les approches différentes pour le faire.

Disclaimer: 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 c'est le cas.

Maintenant, tout cela semble bien et logique, et 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!

Les accessoires de Partridge pour le framework de banc, il est facile de pirater. L'entrée utilisée est le hachage SHA-1 suivant répété 5 000 fois pour former une chaîne longue de 100 000 octets.

209113288F93A9AB8E474EA78D899AFDBB874355

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

18
tne

Ce problème pourrait également être résolu à l'aide d'une table de correspondance. Cela nécessiterait une petite quantité de mémoire statique pour le codeur 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 de décodage 256 octets ou 64 Ko (soit une recherche de caractère unique ou une recherche de caractère double)

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

Lors du décodage, une exception IOException et IndexOutOfRangeException peut se produire (si un caractère a une valeur trop élevée> 256). Des méthodes de/codage des flux ou des tableaux doivent être mises en œuvre, ceci est juste une preuve de concept.

16
drphrozen

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 destiné à supporter également les lettres minuscules

hi = hi + 10 + ((hi >> 31) & 7); est identique à:

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 'a' .. 'f', nous avons de grands nombres et 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...).

15
CoperNick

C'est un excellent post. J'aime la solution de Waleed. Je n'ai pas encore passé le test de patridge mais ça semble être assez rapide. J'avais également besoin du processus inverse, convertissant une chaîne hexadécimale en un tableau d'octets, je l'ai donc écrit comme une inversion de la solution de Waleed. Pas sûr que ce soit plus rapide que la solution originale de Tomalak. Encore une fois, je n’ai pas non plus procédé à l’inverse du 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
Chris F

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
Craig Poulton

Il ne faut pas encombrer les nombreuses réponses ici, mais j’ai trouvé une implémentation assez optimale (~ 4,5x plus qu’acceptée) de l’analyseur de chaînes hexadécimales. Premièrement, le résultat de mes tests (le premier lot est ma mise en œuvre):

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 leur 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 unsafe et en déplaçant la séquence (clairement redondante) caractère-à-grignoter if vers une autre méthode, mais c'était la plus rapide.

(J'admets que cela répond à la moitié de la question. Je pensais que la conversion chaîne-> octet [] était sous-représentée, alors que l'angle de l'octet [] -> semble bien couvert. Ainsi, cette réponse.)

7
Ben Mosher

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 non sécurisées Pour ceux qui préfèrent la performance et qui 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 les tests d'évaluation, l'initialisation de l'alphabet à chaque fois que la fonction de conversion appelée est incorrecte, doit être const (pour chaîne) ou statique en lecture seule (pour char []). La conversion d'octet [] en chaîne, basée sur l'alphabet, devient 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 uniquement mon code" si le code doit être débogué).

5
Maratius

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 Waleed Eissa avec support minuscule:

    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
Geograph

Méthodes d'extension (disclaimer: code totalement 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 les trois solutions de Tomalak (la dernière étant une méthode d'extension sur une chaîne).

3
Pure.Krome

Des développeurs 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();
}

Tandis que ce qui précède est propre à un compact, les accros de la performance en parleront à l’aide d’énumérateurs. Vous pouvez obtenir des performances optimales 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 la plus rapide de toutes les routines que j’ai vu publiées ici jusqu’à présent. Ne prenez pas simplement ma parole pour elle… testez les performances de chaque routine et inspectez vous-même son code CIL.

3
Mark

Pour la performance, j'irais avec une solution de drphrozens. Une optimisation minime pour le décodeur pourrait consister à utiliser une table pour l’un ou l’autre caractère afin de supprimer le "<< 4".

Il est clair que les appels à deux méthodes sont coûteux. Si un type de vérification est effectué sur les données d'entrée ou de sortie (par exemple, CRC, somme de contrôle ou autre), la if (b == 255)... peut être ignorée et, par conséquent, les appels de méthode.

Utiliser offset++ et offset au lieu de offset et offset + 1 pourrait donner un avantage théorique, mais je suppose que le compilateur s'en occupe 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 une idée en tête et n’a pas été testé ni comparé.

2
ClausAndersen

En termes de vitesse, cela semble être mieux que tout ce qui se passe 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
Alexey Borzenkov

Je vais entrer dans cette compétition de bidouillage car j'ai une réponse qui utilise également le bidouillage pour décoder hexadécimaux. Notez que l'utilisation de tableaux de caractères peut être encore plus rapide, car appeler des méthodes StringBuilder prendra é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.

2
Maarten Bodewes

Cette version de ByteArrayToHexViaByteManipulation pourrait être plus rapide.

De mes rapports:

  • ByteArrayToHexViaByteManipulation3: 1,68 ticks moyens (plus de 1000 exécutions), 17,5X
  • ByteArrayToHexViaByteManipulation2: 1,73 ticks moyens (plus de 1000 exécutions), 16,9X
  • ByteArrayToHexViaByteManipulation: 2,90 ticks en moyenne (plus de 1000 exécutions), 10,1X
  • ByteArrayToHexViaLookupAndShift: 3,22 ticks moyens (plus de 1000 exécutions), 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);
    }
2
JoseH

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

Cependant, j’ai eu un certain succès en prenant quelques allusions dans le code de Waleeds et en conciliant cela. 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 de patridges). En fonction de la taille de l'entrée. Commuter autour de?: S pour séparer 0-9 en premier donnerait probablement un résultat légèrement plus rapide puisqu'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;
}
2
Fredrik Hu

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
Stas Makutin

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
spacepille

Une autre méthode consiste à utiliser stackalloc pour réduire la pression de la mémoire du 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);
}
1
Kel

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("-", "");
}
1
Jack Straw

Deux mashups qui réunissent les deux opérations de grignotage.

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

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
1
MCattle

Voici mon coup de feu à cela. J'ai créé une paire de classes d'extension pour étendre chaîne et octet. Sur le test de fichiers volumineux, les performances sont comparables à celles de Byte Manipulation 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 foreach pour itérer et un compteur est plus rapide qu’un indexage explicite for.

Il vient à la 2ème place derrière Byte Manipulation 2 sur ma machine et est un code très lisible. Les résultats de test suivants sont également intéressants:

ToHexStringCharArrayWithCharArrayLookup: 41 589,69 ticks moyens (plus de 1 000 exécutions), 1,5X ToHexStringChar ArrayWithStringLookup:

Sur la base des résultats ci-dessus, il semble raisonnable 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 significatives dans le test de fichier volumineux.
  2. Les pénalités liées à 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;
        }
    }
}

Vous trouverez ci-dessous les résultats des tests obtenus lorsque j'ai inséré mon code dans le projet de test de @ patridge sur ma machine. J'ai également ajouté un test de conversion en tableau d'octets à partir d'hexadécimal. Les tests qui ont exercé mon code sont ByteArrayToHexViaOptimizedLookupAndShift et HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte a été pris à partir de XXXX. 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 représentation de chaîne hexadécimale


ByteArrayToHexViaByteManipulation2: 39 366,64 ticks moyens (plus de 1000 exécutions), 22,4X

ByteArrayToHexViaOptimizedLookupAndShift: 41 588,64 ticks moyens (sur 1 000 exécutions), 21,2X

ByteArrayToHexViaLookup: 55 509,56 ticks moyens (plus de 1000 exécutions), 15,9 fois

ByteArrayToHexViaByteManipulation: 65 349,12 ticks moyens (plus de 1000 exécutions), 13,5 fois

ByteArrayToHexViaLookupAndShift: 86 926,87 ticks moyens (plus de 1000 exécutions), 10,2 fois

ByteArrayToHexStringViaBitConverter: 139 353,73 ticks moyens (plus de 1000 exécutions), 6,3 fois

ByteArrayToHexViaSoapHexBinary: 314 598,77 ticks moyens (plus de 1000 exécutions), 2,8X

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

ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382 623,44 ticks moyens (plus de 1000 exécutions), 2,3 fois

ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818 111,95 ticks moyens (plus de 1000 exécutions), 1,1

ByteArrayToHexStringViaStringConcatArrayConvertAll: 839 244,84 ticks moyens (plus de 1000 exécutions), 1,1X

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

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


1
JamieSee

Si les performances comptent, voici une solution optimisée:

    static readonly char[] _hexDigits = "0123456789abcdef".ToCharArray();
    public static string ToHexString(this byte[] bytes)
    {
        char[] digits = new char[bytes.Length * 2];
        for (int i = 0; i < bytes.Length; i++)
        {
            int d1, d2;
            d1 = Math.DivRem(bytes[i], 16, out d2);
            digits[2 * i] = _hexDigits[d1];
            digits[2 * i + 1] = _hexDigits[d2];
        }
        return new string(digits);
    }

C'est environ 2,5 fois plus rapide que BitConverter.ToString et environ 7 fois plus rapide que BitConverter.ToString + suppression des caractères '-'.

0
Thomas Levesque

Cela fonctionne pour aller de chaîne en tableau d'octets ...

public static byte[] StrToByteArray(string str)
    {
        Dictionary<string, byte> hexindex = new Dictionary<string, byte>();
        for (byte i = 0; i < 255; i++)
            hexindex.Add(i.ToString("X2"), i);

        List<byte> hexres = new List<byte>();
        for (int i = 0; i < str.Length; i += 2)
            hexres.Add(hexindex[str.Substring(i, 2)]);

        return hexres.ToArray();
    }
0
Rick

Je suppose que sa vitesse vaut 16 octets supplémentaires.

    static char[] hexes = new char[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    public static string ToHexadecimal (this byte[] Bytes)
    {
        char[] Result = new char[Bytes.Length << 1];
        int Offset = 0;
        for (int i = 0; i != Bytes.Length; i++) {
            Result[Offset++] = hexes[Bytes[i] >> 4];
            Result[Offset++] = hexes[Bytes[i] & 0x0F];
        }
        return new string(Result);
    }
0
Behrooz

Il y a aussi XmlWriter.WriteBinHex (voir le MSDN page ). Ceci est très utile si vous devez placer la chaîne hexadécimale dans un flux XML.

Voici une méthode autonome pour voir comment cela fonctionne:

    public static string ToBinHex(byte[] bytes)
    {
        XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
        xmlWriterSettings.ConformanceLevel = ConformanceLevel.Fragment;
        xmlWriterSettings.CheckCharacters = false;
        xmlWriterSettings.Encoding = ASCIIEncoding.ASCII;
        MemoryStream memoryStream = new MemoryStream();
        using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, xmlWriterSettings))
        {
            xmlWriter.WriteBinHex(bytes, 0, bytes.Length);
        }
        return Encoding.ASCII.GetString(memoryStream.ToArray());
    }
0
astrada
static string ByteArrayToHexViaLookupPerByte2(byte[] bytes)
{                
        var result3 = new uint[bytes.Length];
        for (int i = 0; i < bytes.Length; i++)
                result3[i] = _Lookup32[bytes[i]];
        var handle = GCHandle.Alloc(result3, GCHandleType.Pinned);
        try
        {
                var result = Marshal.PtrToStringUni(handle.AddrOfPinnedObject(), bytes.Length * 2);
                return result;
        }
        finally
        {
                handle.Free();
        }
}

Cela fonctionne dans mes tests est toujours la deuxième entrée après l'implémentation non sécurisée.

Malheureusement, le banc d’essai n’est pas aussi fiable… si vous le lancez plusieurs fois, la liste est tellement mélangée que qui sait après le non sûr qui est vraiment le plus rapide! Il ne prend pas en compte le préchauffage, le temps de compilation Jit et les succès de performances du GC. J'aurais aimé le réécrire pour avoir plus d'informations, mais je n'avais pas vraiment le temps de le faire.

0
Tommaso Ercole

Je suis venu avec un différent code qui est tolérant aux caractères supplémentaires (espaces, tirets ...) . Il est principalement inspiré de certaines réponses acceptables-rapides ici. Il permet d'analyser le "fichier" suivant

00-aa-84-fb
12 32 FF CD
12 00
12_32_FF_CD
1200d5e68a
/// <summary>Reads a hex string into bytes</summary>
public static IEnumerable<byte> HexadecimalStringToBytes(string hex) {
    if (hex == null)
        throw new ArgumentNullException(nameof(hex));

    char c, c1 = default(char);
    bool hasc1 = false;
    unchecked   {
        for (int i = 0; i < hex.Length; i++) {
            c = hex[i];
            bool isValid = 'A' <= c && c <= 'f' || 'a' <= c && c <= 'f' || '0' <= c && c <= '9';
            if (!hasc1) {
                if (isValid) {
                    hasc1 = true;
                }
            } else {
                hasc1 = false;
                if (isValid) {
                    yield return (byte)((GetHexVal(c1) << 4) + GetHexVal(c));
                }
            }

            c1 = c;
        } 
    }
}

/// <summary>Reads a hex string into a byte array</summary>
public static byte[] HexadecimalStringToByteArray(string hex)
{
    if (hex == null)
        throw new ArgumentNullException(nameof(hex));

    var bytes = new List<byte>(hex.Length / 2);
    foreach (var item in HexadecimalStringToBytes(hex)) {
        bytes.Add(item);
    }

    return bytes.ToArray();
}

private static byte GetHexVal(char val)
{
    return (byte)(val - (val < 0x3A ? 0x30 : val < 0x5B ? 0x37 : 0x57));
    //                   ^^^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^   ^^^^
    //                       digits 0-9       upper char A-Z     a-z
}

Veuillez vous référer à code complet lors de la copie. Tests unitaires inclus.

Certains pourraient dire qu'il est trop tolérant aux caractères supplémentaires. Donc, ne comptez pas sur ce code pour valider (ou le changer).

0
SandRock

Ce qui suit développe l'excellente réponse ici en autorisant également l'option de minuscule native, et gère également les entrées nulles ou vides et en fait une méthode d'extension.

    /// <summary>
    /// Converts the byte array to a hex string very fast. Excellent job
    /// with code lightly adapted from 'community wiki' here: https://stackoverflow.com/a/14333437/264031
    /// (the function was originally named: ByteToHexBitFiddle). Now allows a native lowerCase option
    /// to be input and allows null or empty inputs (null returns null, empty returns empty).
    /// </summary>
    public static string ToHexString(this byte[] bytes, bool lowerCase = false)
    {
        if (bytes == null)
            return null;
        else if (bytes.Length == 0)
            return "";

        char[] c = new char[bytes.Length * 2];

        int b;
        int xAddToAlpha = lowerCase ? 87 : 55;
        int xAddToDigit = lowerCase ? -39 : -7;

        for (int i = 0; i < bytes.Length; i++) {

            b = bytes[i] >> 4;
            c[i * 2] = (char)(xAddToAlpha + b + (((b - 10) >> 31) & xAddToDigit));

            b = bytes[i] & 0xF;
            c[i * 2 + 1] = (char)(xAddToAlpha + b + (((b - 10) >> 31) & xAddToDigit));
        }

        string val = new string(c);
        return val;
    }

    public static string ToHexString(this IEnumerable<byte> bytes, bool lowerCase = false)
    {
        if (bytes == null)
            return null;
        byte[] arr = bytes.ToArray();
        return arr.ToHexString(lowerCase);
    }
0
Nicholas Petersen