Comment pouvez-vous convertir un tableau de bytes en une chaîne hexadécimale et vice versa?
Manquant un point-virgule sur la ligne hex.AppendFormat("{0:x2}", b)
Comment pouvez-vous convertir un tableau de bytes en une chaîne hexadécimale et vice versa?
Vous pouvez utiliser Convert.ToHexString
à partir de .NET 5.
Il existe également une méthode pour l'opération inverse : Convert.FromHexString
.
Pour les anciennes versions de .NET, vous pouvez utiliser :
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 pour le faire, par exemple ici.
La conversion inverse se ferait 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
.
Vous utilisez SubString. Ce n'est pas ce boucle alloue un horrible montant d'objets de chaîne?
Honnêtement - tant que cela ne nuit pas considérablement aux performances, j'aurais tendance à ignorer cela et à faire confiance au runtime et au GC pour s'en occuper.
Remarque: le nouveau chef de 2014-07-31.
J'ai couru les différentes méthodes de conversion par le biais de certains brut Stopwatch
tests de performance, une course avec une phrase aléatoire (n=61, 1000 itérations) et d'une course avec un Projet de Gutenburg texte (n=1,238,957, 150 itérations). Voici les résultats, à peu près du plus rapide au plus lent. Toutes les mesures sont dans les tiques (de 10 000 ticks = 1 ms) et de toute relative des notes par rapport à l' [lente] StringBuilder
mise en œuvre. Pour le code utilisé, voir ci-dessous ou le framework de test repo où j'ai maintenant de maintenir le code pour l'exécution de cette.
AVERTISSEMENT: Ne comptez pas sur ces stats pour quelque chose de concret, ils sont tout simplement un exemple d'exécution de l'échantillon de données. Si vous avez vraiment besoin de haut de gamme de la performance, il est conseillé de tester ces méthodes dans un environnement représentatif de vos besoins de production avec des données représentatives de ce que vous allez utiliser.
BitConverter
(via Tomalak)
{SoapHexBinary}.ToString
(via Mykroft)
{byte}.ToString("X2")
(à l'aide d' foreach
) (dérivé de la Volonté de la Doyenne de la réponse)
{byte}.ToString("X2")
(à l'aide d' {IEnumerable}.Aggregate
, nécessite Système.Linq) (par Marque)
Array.ConvertAll
(à l'aide d' string.Join
) (via Doyen)
Array.ConvertAll
(à l'aide d' string.Concat
, l'exige .NET 4.0) (via Doyen)
{StringBuilder}.AppendFormat
(à l'aide d' foreach
) (via Tomalak)
{StringBuilder}.AppendFormat
(à l'aide d' {IEnumerable}.Aggregate
, nécessite Système.Linq) (dérivé de Tomalak réponse)
Les tables de recherche ont pris de l'avance sur l'octet de manipulation. Fondamentalement, il y a une certaine forme de precomputing ce que tout grignoter ou octet sera dans l'hex. Alors, comme vous le déchirer à travers les données, il vous suffit de regarder jusqu'à la prochaine partie pour voir ce que chaîne hexadécimale de il serait. Cette valeur est ensuite ajouté à la chaîne résultante de sortie d'une certaine façon. Pour un long temps d'octets de manipulation, potentiellement plus difficile à lire par certains développeurs, a été le plus performant de l'approche.
Votre meilleur pari est de trouver des données représentatives et de l'essayer dans un environnement de type production. Si vous avez différentes contraintes de mémoire, vous pouvez préférer une méthode avec moins de dotations de l'un qui serait plus rapide, mais qui consomment le plus de mémoire.
N'hésitez pas à jouer avec les tests de code que j'ai utilisé. Une version est inclus ici, mais n'hésitez pas à cloner le repo et ajouter vos propres méthodes. Veuillez soumettre une demande d'extraction si vous trouvez quelque chose d'intéressant ou souhaitez contribuer à améliorer le cadre de tests qu'il utilise.
Func<byte[], string>
) /Tests/ConvertByteArrayToHexString/Test.cs.TestCandidates
de la valeur de retour dans cette même classe.GenerateTestInput
dans la même classe.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 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();
}
Ajouté Waleed, en réponse à l'analyse. Assez rapide.
Ajouté string.Concat
Array.ConvertAll
de la variante de l'exhaustivité (nécessite .NET 4.0). Sur le pair avec string.Join
version.
Test repo comprend plusieurs variantes telles que StringBuilder.Append(b.ToString("X2"))
. Aucun bouleverser les résultats. foreach
plus rapide que de l' {IEnumerable}.Aggregate
, par exemple, mais BitConverter
gagne toujours.
Ajouté Mykroft de l' SoapHexBinary
réponse à l'analyse, qui a repris la troisième place.
Ajouté CodesInChaos de l'octet de manipulation de réponse, qui a repris la première place (avec une marge sur les gros blocs de texte).
Ajouté Nathan Moinvaziri la recherche de réponse, et la variante de Brian Lambert blog. Les deux plutôt rapide, mais pas prendre la tête de la machine de test, j'ai utilisé (AMD Phenom 9750).
Ajouté @CodesInChaos du nouveau byte-fonction de recherche de réponse. Il semble avoir pris de l'avance sur les deux la phrase tests et le texte intégral des tests.
Voudriez-vous tester le code de la réponse de Waleed? Il semble être très rapide. stackoverflow.com/questions/311165/…
Malgré avoir mis le code à votre disposition pour que vous puissiez faire exactement ce que vous avez demandé par vous-même, j'ai mis à jour le code de test pour inclure la réponse de Waleed. Mis à part la mauvaise humeur, cela va beaucoup plus vite.
J'ai obtenu un résultat différent lorsque j'ai utilisé "ByteArrayToHexStringViaBitConverter" et "ByteArrayToHexStringViaStringBuilder". Ce dernier s'est révélé être "correct". Y a-t-il une raison pour laquelle le résultat des deux fonctions devrait être différent?
Il y a une classe appelée SoapHexBinary qui fait exactement ce que vous voulez.
using System.Runtime.Remoting.Metadata.W3cXsd2001;
public static byte[] GetStringToBytes(string value)
{
SoapHexBinary shb = SoapHexBinary.Parse(value);
return shb.Value;
}
public static string GetBytesToString(byte[] value)
{
SoapHexBinary shb = new SoapHexBinary(value);
return shb.ToString();
}
SoapHexBinary est disponible à partir de .NET 1.0 et se trouve dans mscorlib. Malgré son drôle d'espace de noms, il fait exactement ce que la question demandait.
Super découverte! Notez que vous devrez remplir les chaînes impaires avec un zéro initial pour GetStringToBytes, comme l'autre solution.
Avez-vous vu la mise en œuvre réfléchie ? La réponse acceptée en a une meilleure à mon avis.
Lors de l'écriture de code crypto, il est courant d'éviter les branches dépendantes des données et les recherches de table pour garantir que le temps d'exécution ne dépend pas des données, car un chronométrage dépendant des données peut entraîner des attaques par canaux secondaires.
C'est aussi assez rapide.
static string ByteToHexBitFiddle(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
int b;
for (int i = 0; i < bytes.Length; i++) {
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b-10)>>31)&-7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string(c);
}
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn
Abandonnez tout espoir, vous qui entrez ici
Explication de la manipulation de bits étrange :
bytes[i] >> 4
extrait le nybble élevé d'un octetbytes[i] & 0xF
extrait le nybble faible d'un octetb - 10
< 0
pour les valeurs b < 10
, qui deviendront un chiffre décimal>= 0
pour les valeurs b > 10
, qui deviendront une lettre de A
à F
.i >> 31
sur un entier signé sur 32 bits, on extrait le signe, grâce à l'extension de signe. Ce sera -1
pour i < 0
et 0
pour i >= 0
.(b-10)>>31
sera 0
pour les lettres et -1
pour les chiffres.0
, et b
est dans la plage 10 à 15. Nous voulons le mapper à A
(65) à F
(70), ce qui implique d'ajouter 55 ('A'-10
).b
de la plage 0 à 9 dans la plage 0
(48) à 9
(57). Cela signifie qu'il doit devenir -7 ('0' - 55
).& -7
puisque (0 & -7) == 0
et (-1 & -7) == -7
.Quelques considérations supplémentaires :
c
, car les mesures montrent que le calcul à partir de i
est moins coûteux.i < bytes.Length
comme borne supérieure de la boucle permet au JITter d'éliminer les vérifications des limites sur bytes[i]
, donc j'ai choisi cette variante.b
un int évite des conversions inutiles de et vers byte.La même chose peut être implémentée en utilisant la nouvelle fonction string.Create
, ce qui évite d'avoir à allouer un tableau char[]
séparé.
AggressiveInlining
devrait permettre à cette fonction de disparaître du JIT.32
pour obtenir un résultat en minuscules.Nous pouvons également utiliser Memory
au lieu d'un tableau, cela permet une plus large gamme de tampons mémoire (y compris des tableaux).
static void UppercaseFillHex(Span span, Memory mem) { var bytes = mem.Span; for (int i = 0; i < bytes.Length; i++) { span[i 2] = ConvertNibble(bytes[i] >> 4, 0); span[i 2 + 1] = ConvertNibble(bytes[i] & 0xF, 0); } }
static void LowercaseFillHex(Span span, Memory mem) { var bytes = mem.Span; for (int i = 0; i < bytes.Length; i++) { span[i 2] = ConvertNibble(bytes[i] >> 4, 32); span[i 2 + 1] = ConvertNibble(bytes[i] & 0xF, 32); } }
[MethodImpl(MethodImplOptions.AggressiveInlining)] static char ConvertNibble(int nibble, int adjust) => (char)(55 + adjust + nibble + (((nibble - 10) >> 31) & (-7 - adjust)));
+1 pour avoir correctement cité votre source après avoir invoqué ce petit bout de magie noire. Tous saluent Cthulhu.
Si vous souhaitez plus de flexibilité que BitConverter
, mais que vous ne voulez pas de ces boucles explicites désuètes des années 1990, alors vous pouvez faire :
String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));
Ou, si vous utilisez .NET 4.0 :
String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));
(Ce dernier vient d'un commentaire sur l'article original.)
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.
8 votes
La réponse acceptée ci-dessous semble allouer une quantité horrible de chaînes dans la conversion de chaînes en octets. Je me demande comment cela impacte les performances
9 votes
La classe SoapHexBinary fait exactement ce que vous voulez, je pense.
0 votes
Je viens de commencer à utiliser le C# aujourd'hui et je vois qu'il y a environ 100 pages de réponses ci-dessous à cette question. Est-ce à quoi je peux m'attendre à l'avenir?