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

StackOverflow https://stackoverflow.com/questions/311165

  •  10-07-2019
  •  | 
  •  

Question

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

Était-ce utile?

La solution

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 existe encore plus de variantes, 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 SubString avant de pouvoir supprimer <=>.

Autres conseils

Analyse des performances

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 performance bruts, une exécution avec une phrase aléatoire (n = 61, 1 000 itérations) et une exécution avec un texte du projet Gutenburg (n = 1 238 957, 150 itérations) . Voici les résultats, du plus rapide au plus lent. Toutes les mesures sont en ticks ( 10 000 ticks = 1 ms ) et toutes les notes relatives sont comparées à la mise en oeuvre la plus lente Stopwatch. Pour le code utilisé, voir ci-dessous ou le référentiel de cadre de test , où je conserve maintenant le code permettant de l'exécuter.

Clause de non-responsabilité

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 réellement besoin de performances de premier ordre, testez 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 d'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 type 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 contribuer à améliorer le cadre de test utilisé.

  1. Ajoutez la nouvelle méthode statique (SoapHexBinary) à /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Ajoutez le nom de cette méthode à la <=> valeur de retour de la même classe.
  3. Assurez-vous que vous utilisez la version d'entrée souhaitée, une phrase ou un texte, en basculant les commentaires dans <=> dans la même classe.
  4. Appuyez sur 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. Assez vite.

Mise à jour (2011-10-05)

Ajouté <=> <=> variante pour être complet (nécessite .NET 4.0). À égalité avec <=> version.

Mise à jour (2012-02-05)

Le référentiel de test inclut davantage de variantes telles que <=>. Aucun ne contrarie les résultats. <=> est plus rapide que <=>, par exemple, mais <=> gagne toujours.

Mise à jour (2012-04-03)

Ajout de la <=> réponse à l'analyse de Mykroft, 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é les optimisations d'Airbreather et la <=> variante de cette repo 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.

Il existe une classe appelée SoapHexBinary cela fait exactement ce que vous voulez.

using System.Runtime.Remoting.Metadata.W3cXsd2001;

public static byte[] GetStringToBytes(string value)
{
    SoapHexBinary shb = SoapHexBinary.Parse(value);
    return shb.Value;
}

public static string GetBytesToString(byte[] value)
{
    SoapHexBinary shb = new SoapHexBinary(value);
    return shb.ToString();
}

Lors de l'écriture de code cryptographique, 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 du bizarrement du violoniste:

  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. L'utilisation de i >> 31 sur un entier signé de 32 bits extrait le signe grâce à son extension. Ce sera -1 pour i < 0 et 0 pour i >= 0.
  4. La combinaison des 2) et 3) indique que (b-10)>>31 sera b pour les lettres et 'A'-10 pour les chiffres.
  5. En regardant le cas des lettres, le dernier résumé est 9, et '0' - 55 est compris entre 10 et 15. Nous voulons le mapper de & -7 (65) à (0 & -7) == 0 (70), ce qui implique l’ajout de 55 ((-1 & -7) == -7).
  6. En regardant le cas des chiffres, nous voulons adapter le dernier sommande pour qu'il mappe c de la plage 0 à 9 à la plage i (48) à i < bytes.Length (57). Cela signifie qu'il doit devenir -7 (bytes[i]).
    Maintenant, nous pourrions simplement multiplier par 7. Mais puisque -1 est représenté par 1, tous les bits sont à 1, nous pouvons utiliser <=> puisque <=> et <=>.

Quelques considérations supplémentaires:

  • Je n'ai pas utilisé de seconde variable de boucle pour indexer dans <=>, car la mesure montre que le calcul de <=> est moins cher.
  • Utiliser exactement <=> comme limite supérieure de la boucle permet au JETter d’éliminer les vérifications des limites sur <=>, j’ai donc choisi cette variante.
  • Créer <=> un int permet les conversions inutiles de et en octets.

Si vous voulez plus de souplesse 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.)

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

En fonction de 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 meilleures, ses unsafe frères et soeurs:

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

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 [])

Je viens de rencontrer le même problème aujourd'hui et j'ai découvert ce code:

private static string ByteArrayToHex(byte[] barray)
{
    char[] c = new char[barray.Length * 2];
    byte b;
    for (int i = 0; i < barray.Length; ++i)
    {
        b = ((byte)(barray[i] >> 4));
        c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
        b = ((byte)(barray[i] & 0xF));
        c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
    }
    return new string(c);
}

Source: message du forum octet [] 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 effectué quelques tests de performance du code et il était presque huit fois plus rapide que BitConverter.ToString () (le plus rapide selon le post de patridge).

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

  • Table de l'encodeur 512 & nbsp; octets ou 1024 & nbsp; octets (deux fois la taille si majuscule et minuscule est nécessaire)
  • Table de décodage 256 & nbsp; octets ou 64 & Nbsp; KiB (soit une simple recherche de caractère ou double recherche de caractère)

Ma solution utilise 1024 & nbsp; octets pour la table de codage et 256 & nbsp; octets pour le décodage.

Décodage

private static readonly byte[] LookupTable = new byte[] {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

private static byte Lookup(char c)
{
  var b = LookupTable[c];
  if (b == 255)
    throw new IOException("Expected a hex character, got " + c);
  return b;
}

public static byte ToByte(char[] chars, int offset)
{
  return (byte)(Lookup(chars[offset]) << 4 | Lookup(chars[offset + 1]));
}

Encodage

private static readonly char[][] LookupTableUpper;
private static readonly char[][] LookupTableLower;

static Hex()
{
  LookupTableLower = new char[256][];
  LookupTableUpper = new char[256][];
  for (var i = 0; i < 256; i++)
  {
    LookupTableLower[i] = i.ToString("x2").ToCharArray();
    LookupTableUpper[i] = i.ToString("X2").ToCharArray();
  }
}

public static char[] ToCharLower(byte[] b, int bOffset)
{
  return LookupTableLower[b[bOffset]];
}

public static char[] ToCharUpper(byte[] b, int bOffset)
{
  return LookupTableUpper[b[bOffset]];
}

Comparaison

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

* cette solution

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.

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

Je vais démontrer que cette modification est incorrecte et expliquer pourquoi elle pourrait être annulée. En cours de route, vous découvrirez peut-être quelques notions internes et vous verrez un autre exemple de ce que l’optimisation prématurée est réellement et de la manière dont elle peut vous mordre.

tl; dr: Utilisez simplement Convert.ToByte et String.Substring si vous êtes pressé (& "code original &"; ci-dessous), il s'agit de la meilleure combinaison. si vous ne souhaitez pas réimplémenter Convert.ToByte(char[], Int32). Utilisez quelque chose de plus avancé (voir les autres réponses) qui n’utilise pas StringReader si vous avez besoin de performances. Ne pas utiliser autre chose que numeral en combinaison avec StringReader.Read, à moins que quelqu'un n'ait quelque chose d'intéressant à dire à ce sujet dans les commentaires de cette réponse.

warning: Cette réponse peut devenir obsolète si une _pos surcharge est mise en œuvre 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 que & "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 ce n'est pas le cas, attendez que votre projet soit plus avancé ou attendez la performance (s'il y a un réel besoin, vous définissez l'heure). En attendant, faites la chose la plus simple qui puisse fonctionner à la place.

Code original:

    public static byte[] HexadecimalStringToByteArray_Original(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        for (var i = 0; i < outputLength; i++)
            output[i] = Convert.ToByte(input.Substring(i * 2, 2), 16);
        return output;
    }

Révision 4:

    public static byte[] HexadecimalStringToByteArray_Rev4(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
                output[i] = Convert.ToByte(new string(new char[2] { (char)sr.Read(), (char)sr.Read() }), 16);
        }
        return output;
    }

La révision évite j et utilise un _length à la place. La raison donnée est:

  

Modifier: vous pouvez améliorer les performances des chaînes longues en utilisant un seul   transmettre l'analyseur, comme suit:

Eh bien, consultez le code de référence pour _s , il est clairement & "Single-Pass &"; déjà; et pourquoi ne devrait-il pas en être ainsi? Il fonctionne au niveau octet, pas sur les paires de substitution.

Cependant, il alloue une nouvelle chaîne, mais vous devez quand même en allouer une à Read. 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 String.CopyTo représente un seul octet utilisant deux chiffres (symboles).

Mais alors, pourquoi appeler CopyTo 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éduisez 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 i) que vous auriez pu déclarer vous-même (comme Convert.ToByte(String, Int32) par exemple), une variable de longueur redondante (interne String) et une référence redondante à la chaîne d'entrée (interne <=>) . En d'autres termes, c'est inutile.

Si vous vous demandez comment <=> & "; lit &" ;, il suffit de regarder le code , il ne fait qu'appeler <=> sur la chaîne d'entrée. Le reste, ce n’est que 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; 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 <=> qui s'incrémente par pas de deux parallèlement à <=>? Bien sûr que non, il suffit de multiplier <=> 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 <=> pour attribuer à la chaîne uneSi vous y copiez 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 à partir du tableau et dans la chaîne passez-le dans le constructeur de chaîne). La deuxième copie peut être optimisée en sortie si la chaîne est déjà dans le pool interne, mais alors <=> pourra également l'éviter dans ces cas-là.

En fait, si vous examinez à nouveau <=>, vous constaterez qu'il utilise des connaissances internes de bas niveau sur la structure des chaînes pour allouer la chaîne plus rapidement que vous ne le pouvez normalement, et qu'il intègre le même code que celui utilisé par <=> directement là-bas pour éviter les frais d’appel.

<=>

  • Pire cas: une attribution rapide, une copie rapide.
  • Meilleur cas: aucune affectation, aucune copie.

Méthode manuelle

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

Conclusion? Si vous souhaitez utiliser <=> (car vous ne souhaitez pas ré-implémenter cette fonctionnalité vous-même), il ne semble pas y avoir de moyen de battre <=>; tout ce que vous faites est de tourner en rond, réinventant la roue (uniquement avec des matériaux sous-optimaux).

Notez que l'utilisation de <=> et <=> 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 un <=>, 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 <=>).

Je pense que les personnes qui signalent de meilleures performances en & évitant <=> " évitez également <=>, ce que vous devriez vraiment faire si vous avez quand même besoin de la performance. Regardez les innombrables autres réponses pour découvrir toutes les différentes approches pour le faire.

Clause de non-responsabilité: 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 qu'elle l'est.

Maintenant, tout cela semble bien et logique, espérons 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 to 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

Amusez-vous! (Mais optimisez avec modération.)

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 doit également prendre en charge 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, nous devons donc soustraire 32 de la version par défaut en créant quelques bits 0 en utilisant 'A'.

65 est le code pour '0'

48 est le code pour '9'

7 est le nombre de lettres entre ...456789:;<=>?@ABCD... et <=> dans la table ASCII (<=>).

Ceci est un excellent post. J'aime la solution de Waleed. Je n'ai pas encore passé le test de patridge mais ça a l'air d'ê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;
}

Pourquoi le rendre complexe? C’est simple dans Visual & Nbsp; Studio & Nbsp; 2008:

C #:

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

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")

Je ne veux pas me limiter aux nombreuses réponses ici, mais j’ai trouvé une implémentation assez optimale (~ 4.5x plus qu’acceptée) de l’analyseur de chaîne hexadécimale. 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' permettent de vérifier leur exactitude. Notez qu'ils sont égaux.

La mise en œuvre:

public static byte[] ToByteArrayFromHex(string hexString)
{
  if (hexString.Length % 2 != 0) throw new ArgumentException("String must have an even length");
  var array = new byte[hexString.Length / 2];
  for (int i = 0; i < hexString.Length; i += 2)
  {
    array[i/2] = ByteFromTwoChars(hexString[i], hexString[i + 1]);
  }
  return array;
}

private static byte ByteFromTwoChars(char p, char p_2)
{
  byte ret;
  if (p <= '9' && p >= '0')
  {
    ret = (byte) ((p - '0') << 4);
  }
  else if (p <= 'f' && p >= 'a')
  {
    ret = (byte) ((p - 'a' + 10) << 4);
  }
  else if (p <= 'F' && p >= 'A')
  {
    ret = (byte) ((p - 'A' + 10) << 4);
  } else throw new ArgumentException("Char is not a hex digit: " + p,"p");

  if (p_2 <= '9' && p_2 >= '0')
  {
    ret |= (byte) ((p_2 - '0'));
  }
  else if (p_2 <= 'f' && p_2 >= 'a')
  {
    ret |= (byte) ((p_2 - 'a' + 10));
  }
  else if (p_2 <= 'F' && p_2 >= 'A')
  {
    ret |= (byte) ((p_2 - 'A' + 10));
  } else throw new ArgumentException("Char is not a hex digit: " + p_2, "p_2");

  return ret;
}

J'ai essayé quelques trucs avec unsafe et déplaçant la séquence (nettement redondante) caractère à nibble if vers une autre méthode, mais c’est la méthode la plus rapide.

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

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 la performance et qui n'ont pas peur de la sé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 qu'une 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 Release (avec optimisation) et avec l'option de débogage " Supprimer l'optimisation JIT " désactivé (idem pour & "Activer uniquement mon code &" si le code doit être débogable).

Fonction inverse pour le code Waleed Eissa (chaîne hexadécimale en tableau d'octets):

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

        return b;
    }

Fonction Waleed Eissa 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);
    }

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 Les trois solutions de Tomalak (la dernière étant une méthode d'extension d'une chaîne).

Des développeurs Microsoft, une conversion simple et sympathique:

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 de la performance en parleront avec des é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 vous fiez pas à ma parole. Testez chaque programme de performance et vérifiez son code CIL par vous-même.

En termes de vitesse, cela semble être meilleur 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);
  }

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 fonctionne à 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. Commencer par les points?: S pour séparer 0-9 en premier donnerait probablement un résultat un peu 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;
}

Cette version de ByteArrayToHexViaByteManipulation pourrait être plus rapide.

À partir de mes rapports:

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

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

Pour les performances, j'opterais pour la solution drphrozens. Une optimisation minime pour le décodeur pourrait consister à utiliser une table pour l'un des caractères afin de supprimer le & Quot; & Lt; & Lt; 4 & Quot;.

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), le if (b == 255)... peut être ignoré et, par conséquent, les appels de méthode également.

L'utilisation de offset++ et offset au lieu de offset + 1 et <=> peut présenter un avantage théorique, mais je suppose que le compilateur s'en charge 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 une idée bien précise qui n’a pas été testée ni comparée.

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

Encore une autre variante de 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;          
}

Non optimisé pour la vitesse, mais plus de 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

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

Vous pouvez également utiliser stackalloc pour réduire la pression sur la mémoire du CPG:

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

Voici mon coup de feu. J'ai créé une paire de classes d'extension pour étendre chaîne et octet. Sur le test des 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 en utilisant un foreach pour itérer et un compteur est plus rapide qu'un index explicitement for.

Il se situe à la 2ème place derrière Byte Manipulation 2 sur ma machine et constitue un code très lisible. Les résultats des tests suivants sont également intéressants:

ToHexStringCharArrayWithCharArrayLookup: 41 589,69 ticks moyens (plus de 1000 exécutions), 1,5 fois ToHexStringCharArrayWithStringLookup: 50 764,06 ticks moyens (plus de 1 000 exécutions), 1,2 fois ToHexStringStringBuilderWithCharArrayLookup: 62 812,87 ticks moyens (sur 1000 exécutions), 1,0X

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 Les tableaux de caractères sont importants dans le test de fichiers volumineux.
  2. Pénalités liées à l'utilisation d'un StringBuilder de capacité connue par rapport à un caractère tableau de taille connue pour créer la chaîne sont encore plus significatifs.

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 du tableau d'octets en représentation de chaîne hexadécimale

           

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

     

ByteArrayToHexViaOptimizedLookupAndShift: 41 588,64 ticks moyens   (plus de 1000 exécutions), 21.2X

     

ByteArrayToHexViaLookup: 55 509,56 ticks moyens (plus de 1 000 exécutions), 15,9 x

     

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

     

ByteArrayToHexViaLookupAndShift: 86 926,87 ticks moyens (plus de 1000   fonctionne), 10.2X

     

ByteArrayToHexStringViaBitConverter: moyenne de 139 353,73   ticks (plus de 1000 exécutions), 6.3X

     

ByteArrayToHexViaSoapHexBinary: 314 598,77 ticks moyens (sur 1 000 exécutions), 2,8X

     

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

     

ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382 623,44   ticks moyens (plus de 1000 passages), 2,3X

     

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

     

ByteArrayToHexStringViaStringConcatArrayConvertAll: moyenne de 839 244,84 $   ticks (plus de 1000 exécutions), 1.1X

     

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

     

ByteArrayToHexStringViaStringJoinArrayConvertAll: moyenne de 882 710,28   ticks (plus de 1000 exécutions), 1.0X

     

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;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top