Comment pouvez-vous convertir un tableau d'octets en une chaîne hexadécimale et vice versa?
Comment pouvez-vous convertir un tableau d'octets en une chaîne hexadécimale et vice versa?
Réponses:
Soit:
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
ou:
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
Il y a encore plus de variantes de le faire, par exemple ici .
La conversion inverse se passerait comme ceci:
public static byte[] StringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
L'utilisation 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 abandonner SubString
.
Remarque: nouveau leader au 20/08/2015.
J'ai exécuté chacune des différentes méthodes de conversion à travers des Stopwatch
tests de performances bruts , une analyse avec une phrase aléatoire (n = 61, 1000 itérations) et une analyse avec un texte Project Gutenburg (n = 1238957, 150 itérations). Voici les résultats, du plus rapide au plus lent. Toutes les mesures sont en ticks ( 10 000 ticks = 1 ms ) et toutes les notes relatives sont comparées à l' StringBuilder
implémentation [la plus lente] . Pour le code utilisé, voir ci-dessous ou le référentiel du framework de test où je maintiens maintenant le code pour l'exécuter.
AVERTISSEMENT: ne vous fiez pas à ces statistiques pour quoi que ce soit de concret; ce sont simplement des échantillons de données. Si vous avez vraiment besoin de performances de premier ordre, veuillez tester ces méthodes dans un environnement représentatif de vos besoins de production avec des données représentatives de ce que vous utiliserez.
unsafe
(via CodesInChaos) (ajouté au test repo par airbreather )
BitConverter
(via Tomalak)
{SoapHexBinary}.ToString
(via Mykroft)
{byte}.ToString("X2")
(en utilisant foreach
) (dérivé de la réponse de Will Dean)
{byte}.ToString("X2")
(utilisation {IEnumerable}.Aggregate
, nécessite System.Linq) (via Mark)
Array.ConvertAll
(en utilisant string.Join
) (via Will Dean)
Array.ConvertAll
(en utilisant string.Concat
, nécessite .NET 4.0) (via Will Dean)
{StringBuilder}.AppendFormat
(en utilisant foreach
) (via Tomalak)
{StringBuilder}.AppendFormat
(en utilisant {IEnumerable}.Aggregate
, nécessite System.Linq) (dérivé de la réponse de Tomalak)
Les tables de recherche ont pris le pas sur la manipulation des octets. Fondamentalement, il existe une certaine forme de précalcul du grignotage ou de l'octet donné en hexadécimal. Ensuite, lorsque vous extrayez les données, vous recherchez simplement la partie suivante pour voir quelle chaîne hexadécimale il s'agirait. Cette valeur est ensuite ajoutée à la sortie de chaîne résultante d'une certaine manière. Pendant longtemps, la manipulation d'octets, potentiellement plus difficile à lire par certains développeurs, a été l'approche la plus performante.
Votre meilleur pari sera toujours de trouver des données représentatives et de les essayer dans un environnement de production. Si vous avez des contraintes de mémoire différentes, vous pouvez préférer une méthode avec moins d'allocations à une qui serait plus rapide mais consommerait plus de mémoire.
N'hésitez pas à jouer avec le code de test que j'ai utilisé. Une version est incluse ici, mais n'hésitez pas à cloner le dépôt et à ajouter vos propres méthodes. Veuillez soumettre une demande d'extraction si vous trouvez quelque chose d'intéressant ou souhaitez aider à améliorer le cadre de test qu'il utilise.
Func<byte[], string>
) à /Tests/ConvertByteArrayToHexString/Test.cs.TestCandidates
valeur de retour dans cette même classe.GenerateTestInput
dans cette 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 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();
}
Ajout de la réponse de Waleed à l'analyse. Tres rapide.
Ajout d'une string.Concat
Array.ConvertAll
variante pour être complet (nécessite .NET 4.0). Au même niveau que la string.Join
version.
Le repo de test comprend plus de variantes telles que StringBuilder.Append(b.ToString("X2"))
. Aucun n'a bouleversé les résultats. foreach
est plus rapide que {IEnumerable}.Aggregate
, par exemple, mais BitConverter
gagne toujours.
Ajout de la SoapHexBinary
réponse de Mykroft à l'analyse, qui a pris la troisième place.
Ajout de la réponse de manipulation d'octets de CodesInChaos, qui a pris la première place (par une grande marge sur de gros blocs de texte).
Ajout de la réponse de recherche de Nathan Moinvaziri et de la variante du blog de Brian Lambert. Tous deux assez rapides, mais ne prenant pas les devants sur la machine de test que j'ai utilisée (AMD Phenom 9750).
Ajout de la nouvelle réponse de recherche basée sur octets de @ CodesInChaos. Il semble avoir pris la tête des tests de phrases et des tests de texte intégral.
Ajout des optimisations et de la unsafe
variante de l' aérographe au dépôt de cette réponse . Si vous voulez jouer dans le jeu dangereux, vous pouvez obtenir d'énormes gains de performance par rapport à l'un des meilleurs gagnants précédents sur les chaînes courtes et les gros textes.
bytes.ToHexStringAtLudicrousSpeed()
).
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();
}
Lors de l'écriture de code cryptographique, il est courant d'éviter les branches dépendantes des données et les recherches de table pour garantir que l'exécution ne dépend pas des données, car le timing dépendant des données peut conduire à des attaques par canal latéral.
C'est aussi assez rapide.
static string ByteToHexBitFiddle(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
int b;
for (int i = 0; i < bytes.Length; i++) {
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b-10)>>31)&-7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string(c);
}
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn
Abandonnez tout espoir, vous qui entrez ici
Une explication du bricolage bizarre:
bytes[i] >> 4
extrait le quartet haut d'un octet bytes[i] & 0xF
extrait le quartet bas d'un octetb - 10
< 0
pour les valeurs b < 10
, qui deviendra un chiffre décimal >= 0
pour les valeurs b > 10
, qui deviendra une lettre de A
à F
.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
.(b-10)>>31
sera 0
pour les lettres et -1
pour les chiffres.0
et se b
situe dans la plage de 10 à 15. Nous voulons la mapper sur A
(65) à F
(70), ce qui implique d'ajouter 55 ( 'A'-10
).b
de la plage 0 à 9 à la plage 0
(48) à 9
(57). Cela signifie qu'il doit devenir -7 ( '0' - 55
). & -7
depuis (0 & -7) == 0
et (-1 & -7) == -7
.Quelques considérations supplémentaires:
c
, car la mesure montre que son calcul i
est moins cher.i < bytes.Length
la limite supérieure de la boucle permet au JITter d'éliminer les vérifications de limites bytes[i]
, j'ai donc choisi cette variante.b
un int permet des conversions inutiles de et vers octet.hex string
à byte[] array
?
87 + b + (((b-10)>>31)&-39)
byte[] array
", ce qui signifie littéralement un tableau de tableaux d'octets, ou byte[][]
. Je me moquais juste.
Si vous voulez plus de flexibilité que BitConverter
, mais ne voulez pas ces boucles explicites de style maladroit des années 1990, alors vous pouvez faire:
String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));
Ou, si vous utilisez .NET 4.0:
String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));
(Ce dernier à partir d'un commentaire sur le message d'origine.)
Une autre approche basée sur une table de recherche. Celui-ci utilise une seule table de recherche pour chaque octet, au lieu d'une table de recherche par quartet.
private static readonly uint[] _lookup32 = CreateLookup32();
private static uint[] CreateLookup32()
{
var result = new uint[256];
for (int i = 0; i < 256; i++)
{
string s=i.ToString("X2");
result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
}
return result;
}
private static string ByteArrayToHexViaLookup32(byte[] bytes)
{
var lookup32 = _lookup32;
var result = new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
var val = lookup32[bytes[i]];
result[2*i] = (char)val;
result[2*i + 1] = (char) (val >> 16);
}
return new string(result);
}
J'ai aussi testé des variantes de ce en utilisant ushort
, struct{char X1, X2}
,struct{byte X1, X2}
dans la table de consultation.
Selon la cible de compilation (x86, X64), ceux-ci avaient à peu près les mêmes performances ou étaient légèrement plus lents que cette variante.
Et pour des performances encore plus élevées, son unsafe
frère:
private static readonly uint[] _lookup32Unsafe = CreateLookup32Unsafe();
private static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();
private static uint[] CreateLookup32Unsafe()
{
var result = new uint[256];
for (int i = 0; i < 256; i++)
{
string s=i.ToString("X2");
if(BitConverter.IsLittleEndian)
result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
else
result[i] = ((uint)s[1]) + ((uint)s[0] << 16);
}
return result;
}
public static string ByteArrayToHexViaLookup32Unsafe(byte[] bytes)
{
var lookupP = _lookup32UnsafeP;
var result = new char[bytes.Length * 2];
fixed(byte* bytesP = bytes)
fixed (char* resultP = result)
{
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++)
{
resultP2[i] = lookupP[bytesP[i]];
}
}
return new string(result);
}
Ou si vous jugez acceptable d'écrire directement dans la chaîne:
public static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes)
{
var lookupP = _lookup32UnsafeP;
var result = new string((char)0, bytes.Length * 2);
fixed (byte* bytesP = bytes)
fixed (char* resultP = result)
{
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++)
{
resultP2[i] = lookupP[bytesP[i]];
}
}
return result;
}
Span
peut être utilisé maintenant au lieu de unsafe
??
Vous pouvez utiliser la méthode BitConverter.ToString:
byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256}
Console.WriteLine( BitConverter.ToString(bytes));
Production:
00-01-02-04-08-10-20-40-80-FF
Pour plus d'informations: Méthode BitConverter.ToString (octet [])
Je viens de rencontrer le même problème aujourd'hui, et je suis tombé sur ce code:
private static string ByteArrayToHex(byte[] barray)
{
char[] c = new char[barray.Length * 2];
byte b;
for (int i = 0; i < barray.Length; ++i)
{
b = ((byte)(barray[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
Source: Forum post byte [] Array to Hex String (voir l'article de PZahra). J'ai un peu modifié le code pour supprimer le préfixe 0x.
J'ai fait des tests de performances sur le code et c'était presque huit fois plus rapide que d'utiliser BitConverter.ToString () (le plus rapide selon le post de patridge).
Il s'agit d'une réponse à la révision 4 de la réponse très populaire de Tomalak (et des modifications ultérieures).
Je ferai valoir que cette modification est erronée et expliquerai pourquoi elle pourrait être annulée. En cours de route, vous pourriez apprendre une ou deux choses sur certains éléments internes et voir encore un autre exemple de ce qu'est vraiment l'optimisation prématurée et comment elle peut vous mordre.
tl; dr: utilisez simplement Convert.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 les autres réponses) qui ne sert pas Convert.ToByte
si vous avez besoin de performances. Ne pas utiliser autre chose que , String.Substring
en combinaison avec Convert.ToByte
, à moins que quelqu'un a quelque chose d' intéressant à dire à ce sujet dans les commentaires de cette réponse.
avertissement: cette réponse peut devenir obsolète si une Convert.ToByte(char[], Int32)
surcharge est implémentée dans le framework. Il est peu probable que cela se produise bientôt.
En règle générale, je n'aime pas beaucoup dire "ne pas optimiser prématurément", car personne ne sait quand "prématuré" est. La seule chose que vous devez considérer lorsque vous décidez d'optimiser ou non est: "Ai-je le temps et les ressources nécessaires pour étudier correctement les approches d'optimisation?". Si vous ne le faites pas, alors il est trop tôt, attendez que votre projet est plus mature ou jusqu'à ce que vous avez besoin de la performance (s'il y a un réel besoin, alors vous rendre le temps). En attendant, faites la chose la plus simple qui pourrait fonctionner à la place.
Code d'origine:
public static byte[] HexadecimalStringToByteArray_Original(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
for (var i = 0; i < outputLength; i++)
output[i] = Convert.ToByte(input.Substring(i * 2, 2), 16);
return output;
}
Révision 4:
public static byte[] HexadecimalStringToByteArray_Rev4(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
output[i] = Convert.ToByte(new string(new char[2] { (char)sr.Read(), (char)sr.Read() }), 16);
}
return output;
}
La révision évite String.Substring
et utilise un à la StringReader
place. La raison donnée est:
Edit: vous pouvez améliorer les performances des chaînes longues en utilisant un analyseur en un seul passage, comme ceci:
Eh bien, en regardant le code de référence pourString.Substring
, il est déjà clairement "en un seul passage"; et pourquoi ne le serait-il pas? Il fonctionne au niveau de l'octet, pas sur des paires de substitution.
Cependant, il alloue une nouvelle chaîne, mais vous devez Convert.ToByte
quand même en allouer une à laquelle passer . De plus, la solution fournie dans la révision alloue encore un autre objet à chaque itération (le tableau à deux caractères); vous pouvez mettre cette allocation en toute sécurité en dehors de la boucle et réutiliser le tableau pour éviter cela.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
{
numeral[0] = (char)sr.Read();
numeral[1] = (char)sr.Read();
output[i] = Convert.ToByte(new string(numeral), 16);
}
}
return output;
}
Chaque hexadécimal numeral
représente un seul octet utilisant 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éduisez de deux le nombre d'appels.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
{
var read = sr.Read(numeral, 0, 2);
Debug.Assert(read == 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
}
return output;
}
Il vous reste un lecteur de chaîne dont la seule "valeur" ajoutée est un index parallèle (interne _pos
) que vous auriez pu déclarer vous-même (comme j
par exemple), une variable de longueur redondante (interne _length
) et une référence redondante à l'entrée chaîne (interne _s
). En d'autres termes, c'est inutile.
Si vous vous demandez comment Read
"lit", regardez simplement le code , il ne fait qu'appeler String.CopyTo
sur la chaîne d'entrée. Le reste est juste des frais généraux de tenue de livres pour maintenir des valeurs dont nous n'avons pas besoin.
Donc, retirez déjà le lecteur de chaîne et appelez- CopyTo
vous; 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 j
index 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 être en mesure d'optimiser pour un ajout).
public static byte[] HexadecimalStringToByteArray_BestEffort(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
for (int i = 0; i < outputLength; i++)
{
input.CopyTo(i * 2, numeral, 0, 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
return output;
}
À quoi ressemble la solution maintenant? Exactement comme au début, mais au lieu d'utiliser String.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 recopiez les données à partir de le tableau et dans la chaîne (lorsque vous le passez dans le constructeur de chaîne). La deuxième copie peut être optimisée si la chaîne est déjà dans le pool interne, mais String.Substring
pourra également l'éviter dans ces cas.
En fait, si vous regardez à String.Substring
nouveau, vous voyez qu'il utilise des connaissances internes de bas niveau sur la façon dont les chaînes sont construites pour allouer la chaîne plus rapidement que vous ne le feriez normalement, et il insère le même code utilisé CopyTo
directement par là pour éviter les frais généraux d'appel.
String.Substring
Méthode manuelle
Conclusion? Si vous voulez utiliserConvert.ToByte(String, Int32)
(parce que vous ne voulez pas réimplémenter cette fonctionnalité vous-même), il ne semble pas y avoir de moyen de battre String.Substring
; tout ce que vous faites, c'est tourner en rond, réinventer la roue (uniquement avec des matériaux sous-optimaux).
Notez que l'utilisation de Convert.ToByte
et String.Substring
est un choix parfaitement valide si vous n'avez pas besoin de performances extrêmes. N'oubliez pas: n'optez pour une alternative que si vous avez le temps et les ressources pour enquêter sur son bon fonctionnement.
S'il y en avait un Convert.ToByte(char[], Int32)
, les choses seraient bien sûr différentes (il serait possible de faire ce que je viens de décrire et d'éviter complètement String
).
Je soupçonne que les personnes qui signalent de meilleures performances en «évitant String.Substring
» évitent également Convert.ToByte(String, Int32)
, ce que vous devriez vraiment faire si vous avez besoin de la performance de toute façon. Regardez les innombrables autres réponses pour découvrir toutes les différentes approches pour le faire.
Avertissement: je n'ai pas décompilé la dernière version du framework pour vérifier que la source de référence est à jour, je suppose que oui.
Maintenant, tout cela semble bien et logique, j'espère même évident si vous avez réussi à aller aussi loin. Mais est-ce vrai?
Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz
Cores: 8
Current Clock Speed: 2600
Max Clock Speed: 2600
--------------------
Parsing hexadecimal string into an array of bytes
--------------------
HexadecimalStringToByteArray_Original: 7,777.09 average ticks (over 10000 runs), 1.2X
HexadecimalStringToByteArray_BestEffort: 8,550.82 average ticks (over 10000 runs), 1.1X
HexadecimalStringToByteArray_Rev4: 9,218.03 average ticks (over 10000 runs), 1.0X
Oui!
Props à Partridge pour le cadre de banc, il est facile de pirater. L'entrée utilisée est le hachage SHA-1 suivant répété 5000 fois pour créer une chaîne longue de 100 000 octets.
209113288F93A9AB8E474EA78D899AFDBB874355
S'amuser! (Mais optimisez avec modération.)
Complément pour répondre par @CodesInChaos (méthode inversée)
public static byte[] HexToByteUsingByteManipulation(string s)
{
byte[] bytes = new byte[s.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
int hi = s[i*2] - 65;
hi = hi + 10 + ((hi >> 31) & 7);
int lo = s[i*2 + 1] - 65;
lo = lo + 10 + ((lo >> 31) & 7) & 0x0f;
bytes[i] = (byte) (lo | hi << 4);
}
return bytes;
}
Explication:
& 0x0f
est de prendre en charge également les lettres minuscules
hi = hi + 10 + ((hi >> 31) & 7);
est le même que:
hi = ch-65 + 10 + (((ch-65) >> 31) & 7);
Pour '0' .. '9' c'est la même chose que hi = ch - 65 + 10 + 7;
c'est hi = ch - 48
(c'est à cause de 0xffffffff & 7
).
Pour 'A' .. 'F' c'est hi = ch - 65 + 10;
(c'est à cause de 0x00000000 & 7
).
Pour 'a' .. 'f' nous devons faire de grands nombres, nous devons donc soustraire 32 de la version par défaut en faisant quelques bits 0
en utilisant & 0x0f
.
65 est le code pour 'A'
48 est le code pour '0'
7 est le nombre de lettres entre '9'
et 'A'
dans la table ASCII ( ...456789:;<=>?@ABCD...
).
Ce problème peut également être résolu à l'aide d'une table de correspondance. Cela nécessiterait une petite quantité de mémoire statique pour l'encodeur et le décodeur. Cette méthode sera cependant rapide:
Ma solution utilise 1024 octets pour la table de codage et 256 octets pour le 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]));
}
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]];
}
StringBuilderToStringFromBytes: 106148
BitConverterToStringFromBytes: 15783
ArrayConvertAllToStringFromBytes: 54290
ByteManipulationToCharArray: 8444
TableBasedToCharArray: 5651 *
* cette solution
Pendant le décodage, IOException et IndexOutOfRangeException peuvent se produire (si un caractère a une valeur trop élevée> 256). Des méthodes de dé / codage des flux ou des tableaux doivent être implémentées, ce n'est qu'une preuve de concept.
Ceci est un excellent article. J'aime la solution de Waleed. Je ne l'ai pas passé à travers le test de patridge, mais il semble être assez rapide. J'avais également besoin du processus inverse, convertissant une chaîne hexadécimale en un tableau d'octets, donc je l'ai écrit comme une inversion de la solution de Waleed. Je ne sais pas si c'est plus rapide que la solution originale de Tomalak. Encore une fois, je n'ai pas non plus exécuté le processus inverse via le test de patridge.
private byte[] HexStringToByteArray(string hexString)
{
int hexStringLength = hexString.Length;
byte[] b = new byte[hexStringLength / 2];
for (int i = 0; i < hexStringLength; i += 2)
{
int topChar = (hexString[i] > 0x40 ? hexString[i] - 0x37 : hexString[i] - 0x30) << 4;
int bottomChar = hexString[i + 1] > 0x40 ? hexString[i + 1] - 0x37 : hexString[i + 1] - 0x30;
b[i / 2] = Convert.ToByte(topChar + bottomChar);
}
return b;
}
hexString[i] &= ~0x20;
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("-", "")
Ne pas empiler les nombreuses réponses ici, mais j'ai trouvé une implémentation assez optimale (~ 4,5x meilleure qu'acceptée), simple de l'analyseur de chaîne hexadécimal. Tout d'abord, sortie de mes tests (le premier lot est mon implémentation):
Give me that string:
04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939f
Time to parse 100,000 times: 50.4192 ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Accepted answer: (StringToByteArray)
Time to parse 100000 times: 233.1264ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With Mono's implementation:
Time to parse 100000 times: 777.2544ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With SoapHexBinary:
Time to parse 100000 times: 845.1456ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Les lignes base64 et «BitConverter'd» sont là pour tester l'exactitude. Notez qu'ils sont égaux.
La mise en oeuvre:
public static byte[] ToByteArrayFromHex(string hexString)
{
if (hexString.Length % 2 != 0) throw new ArgumentException("String must have an even length");
var array = new byte[hexString.Length / 2];
for (int i = 0; i < hexString.Length; i += 2)
{
array[i/2] = ByteFromTwoChars(hexString[i], hexString[i + 1]);
}
return array;
}
private static byte ByteFromTwoChars(char p, char p_2)
{
byte ret;
if (p <= '9' && p >= '0')
{
ret = (byte) ((p - '0') << 4);
}
else if (p <= 'f' && p >= 'a')
{
ret = (byte) ((p - 'a' + 10) << 4);
}
else if (p <= 'F' && p >= 'A')
{
ret = (byte) ((p - 'A' + 10) << 4);
} else throw new ArgumentException("Char is not a hex digit: " + p,"p");
if (p_2 <= '9' && p_2 >= '0')
{
ret |= (byte) ((p_2 - '0'));
}
else if (p_2 <= 'f' && p_2 >= 'a')
{
ret |= (byte) ((p_2 - 'a' + 10));
}
else if (p_2 <= 'F' && p_2 >= 'A')
{
ret |= (byte) ((p_2 - 'A' + 10));
} else throw new ArgumentException("Char is not a hex digit: " + p_2, "p_2");
return ret;
}
J'ai essayé quelques trucs avec unsafe
et déplacé la if
séquence caractère à grignoter (clairement redondante) vers une autre méthode, mais c'était la plus rapide.
(Je concède que cela répond à la moitié de la question. J'ai senti que la conversion chaîne -> octet [] était sous-représentée, tandis que l'angle octet [] -> chaîne semble être bien couvert. Ainsi, cette réponse.)
Versions sûres:
public static class HexHelper
{
[System.Diagnostics.Contracts.Pure]
public static string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string hexAlphabet = @"0123456789ABCDEF";
var chars = new char[checked(value.Length * 2)];
unchecked
{
for (int i = 0; i < value.Length; i++)
{
chars[i * 2] = hexAlphabet[value[i] >> 4];
chars[i * 2 + 1] = hexAlphabet[value[i] & 0xF];
}
}
return new string(chars);
}
[System.Diagnostics.Contracts.Pure]
public static byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = value[i * 2]; // High 4 bits.
int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
b = value[i * 2 + 1]; // Low 4 bits.
val += (b - '0') + ((('9' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
return result;
}
}
}
Versions dangereuses Pour ceux qui préfèrent les performances et n'ont pas peur de l'insécurité. ToHex environ 35% plus rapide et FromHex 10% plus rapide.
public static class HexUnsafeHelper
{
[System.Diagnostics.Contracts.Pure]
public static unsafe string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string alphabet = @"0123456789ABCDEF";
string result = new string(' ', checked(value.Length * 2));
fixed (char* alphabetPtr = alphabet)
fixed (char* resultPtr = result)
{
char* ptr = resultPtr;
unchecked
{
for (int i = 0; i < value.Length; i++)
{
*ptr++ = *(alphabetPtr + (value[i] >> 4));
*ptr++ = *(alphabetPtr + (value[i] & 0xF));
}
}
}
return result;
}
[System.Diagnostics.Contracts.Pure]
public static unsafe byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
fixed (char* valuePtr = value)
{
char* valPtr = valuePtr;
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = *valPtr++; // High 4 bits.
int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
b = *valPtr++; // Low 4 bits.
val += (b - '0') + ((('9' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
}
return result;
}
}
}
BTW Pour tester les tests d'initialisation de l'alphabet à chaque fois que la fonction de conversion appelée est incorrecte, l'alphabet doit être const (pour la chaîne) ou statique en lecture seule (pour char []). La conversion alphabétique de l'octet [] en chaîne devient alors aussi rapide que les versions de manipulation d'octets.
Et bien sûr, le test doit être compilé dans la version (avec optimisation) et avec l'option de débogage "Supprimer l'optimisation JIT" désactivée (idem pour "Activer Just My Code" si le code doit être débogable).
Fonction inverse pour le code Waleed Eissa (Hex String To Byte Array):
public static byte[] HexToBytes(this string hexString)
{
byte[] b = new byte[hexString.Length / 2];
char c;
for (int i = 0; i < hexString.Length / 2; i++)
{
c = hexString[i * 2];
b[i] = (byte)((c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57)) << 4);
c = hexString[i * 2 + 1];
b[i] += (byte)(c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57));
}
return b;
}
Fonction Eissa Waleed avec prise en charge des minuscules:
public static string BytesToHex(this byte[] barray, bool toLowerCase = true)
{
byte addByte = 0x37;
if (toLowerCase) addByte = 0x57;
char[] c = new char[barray.Length * 2];
byte b;
for (int i = 0; i < barray.Length; ++i)
{
b = ((byte)(barray[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + addByte : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + addByte : b + 0x30);
}
return new string(c);
}
Méthodes d'extension (avertissement: code complètement non testé, BTW ...):
public static class ByteExtensions
{
public static string ToHexString(this byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
{
hex.AppendFormat("{0:x2}", b);
}
return hex.ToString();
}
}
etc. Utilisez l'une des trois solutions de Tomalak (la dernière étant une méthode d'extension sur une chaîne).
Des développeurs de Microsoft, une conversion simple et agréable:
public static string ByteArrayToString(byte[] ba)
{
// Concatenate the bytes into one long string
return ba.Aggregate(new StringBuilder(32),
(sb, b) => sb.Append(b.ToString("X2"))
).ToString();
}
Bien que ce qui précède soit propre et compact, les accros à la performance en crieront à l'aide d'énumérateurs. Vous pouvez obtenir des performances optimales avec une version améliorée de la réponse originale de Tomalak :
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
for(int i=0; i < ba.Length; i++) // <-- Use for loop is faster than foreach
hex.Append(ba[i].ToString("X2")); // <-- ToString is faster than AppendFormat
return hex.ToString();
}
C'est la plus rapide de toutes les routines que j'ai vues jusqu'à présent. Ne vous fiez pas seulement à ma parole ... testez les performances de chaque routine et inspectez vous-même son code CIL.
b.ToSting("X2")
.
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("-", "");
}
Source == null
ou Source.Length == 0
nous avons un problème monsieur!
En termes de vitesse, cela semble être mieux que tout ici:
public static string ToHexString(byte[] data) {
byte b;
int i, j, k;
int l = data.Length;
char[] r = new char[l * 2];
for (i = 0, j = 0; i < l; ++i) {
b = data[i];
k = b >> 4;
r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
k = b & 15;
r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
}
return new string(r);
}
Je n'ai pas obtenu le code que vous avez suggéré de travailler, Olipro. hex[i] + hex[i+1]
apparemment retourné un int
.
J'ai cependant eu un certain succès en prenant quelques indices du code Waleeds et en martelant cela ensemble. C'est moche comme l'enfer mais il semble fonctionner et performer à 1/3 du temps par rapport aux autres selon mes tests (en utilisant le mécanisme de test des ponts). Selon la taille d'entrée. Changer le?: S pour séparer d'abord 0-9 donnerait probablement un résultat légèrement plus rapide car il y a plus de chiffres que de lettres.
public static byte[] StringToByteArray2(string hex)
{
byte[] bytes = new byte[hex.Length/2];
int bl = bytes.Length;
for (int i = 0; i < bl; ++i)
{
bytes[i] = (byte)((hex[2 * i] > 'F' ? hex[2 * i] - 0x57 : hex[2 * i] > '9' ? hex[2 * i] - 0x37 : hex[2 * i] - 0x30) << 4);
bytes[i] |= (byte)(hex[2 * i + 1] > 'F' ? hex[2 * i + 1] - 0x57 : hex[2 * i + 1] > '9' ? hex[2 * i + 1] - 0x37 : hex[2 * i + 1] - 0x30);
}
return bytes;
}
Cette version de ByteArrayToHexViaByteManipulation pourrait être plus rapide.
De mes rapports:
...
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 entrer dans cette compétition de bidouilles de bits car j'ai une réponse qui utilise également des bidouilles de bits pour décoder les hexadécimaux. Notez que l'utilisation de tableaux de caractères peut être encore plus rapide car les StringBuilder
méthodes d' appel prendront également du temps.
public static String ToHex (byte[] data)
{
int dataLength = data.Length;
// pre-create the stringbuilder using the length of the data * 2, precisely enough
StringBuilder sb = new StringBuilder (dataLength * 2);
for (int i = 0; i < dataLength; i++) {
int b = data [i];
// check using calculation over bits to see if first tuple is a letter
// isLetter is zero if it is a digit, 1 if it is a letter
int isLetter = (b >> 7) & ((b >> 6) | (b >> 5)) & 1;
// calculate the code using a multiplication to make up the difference between
// a digit character and an alphanumerical character
int code = '0' + ((b >> 4) & 0xF) + isLetter * ('A' - '9' - 1);
// now append the result, after casting the code point to a character
sb.Append ((Char)code);
// do the same with the lower (less significant) tuple
isLetter = (b >> 3) & ((b >> 2) | (b >> 1)) & 1;
code = '0' + (b & 0xF) + isLetter * ('A' - '9' - 1);
sb.Append ((Char)code);
}
return sb.ToString ();
}
public static byte[] FromHex (String hex)
{
// pre-create the array
int resultLength = hex.Length / 2;
byte[] result = new byte[resultLength];
// set validity = 0 (0 = valid, anything else is not valid)
int validity = 0;
int c, isLetter, value, validDigitStruct, validDigit, validLetterStruct, validLetter;
for (int i = 0, hexOffset = 0; i < resultLength; i++, hexOffset += 2) {
c = hex [hexOffset];
// check using calculation over bits to see if first char is a letter
// isLetter is zero if it is a digit, 1 if it is a letter (upper & lowercase)
isLetter = (c >> 6) & 1;
// calculate the tuple value using a multiplication to make up the difference between
// a digit character and an alphanumerical character
// minus 1 for the fact that the letters are not zero based
value = ((c & 0xF) + isLetter * (-1 + 10)) << 4;
// check validity of all the other bits
validity |= c >> 7; // changed to >>, maybe not OK, use UInt?
validDigitStruct = (c & 0x30) ^ 0x30;
validDigit = ((c & 0x8) >> 3) * (c & 0x6);
validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);
validLetterStruct = c & 0x18;
validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
validity |= isLetter * (validLetterStruct | validLetter);
// do the same with the lower (less significant) tuple
c = hex [hexOffset + 1];
isLetter = (c >> 6) & 1;
value ^= (c & 0xF) + isLetter * (-1 + 10);
result [i] = (byte)value;
// check validity of all the other bits
validity |= c >> 7; // changed to >>, maybe not OK, use UInt?
validDigitStruct = (c & 0x30) ^ 0x30;
validDigit = ((c & 0x8) >> 3) * (c & 0x6);
validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);
validLetterStruct = c & 0x18;
validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
validity |= isLetter * (validLetterStruct | validLetter);
}
if (validity != 0) {
throw new ArgumentException ("Hexadecimal encoding incorrect for input " + hex);
}
return result;
}
Converti à partir du code Java.
Char[]
et l'utiliser en Char
interne au lieu des pouces ...
Pour les performances, j'irais avec la solution drphrozens. Une minuscule optimisation pour le décodeur pourrait être d'utiliser une table pour chaque caractère pour se débarrasser du "<< 4".
De toute évidence, les deux appels de méthode sont coûteux. Si une sorte de vérification est effectuée sur les données d'entrée ou de sortie (peut être CRC, somme de contrôle ou autre), cela if (b == 255)...
peut être ignoré et, par conséquent, la méthode appelle également.
Utiliser offset++
et offset
au lieu de offset
et offset + 1
pourrait donner des avantages théoriques, mais je soupçonne que le compilateur gère cela mieux que moi.
private static readonly byte[] LookupTableLow = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static readonly byte[] LookupTableHigh = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static byte LookupLow(char c)
{
var b = LookupTableLow[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
private static byte LookupHigh(char c)
{
var b = LookupTableHigh[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
public static byte ToByte(char[] chars, int offset)
{
return (byte)(LookupHigh(chars[offset++]) | LookupLow(chars[offset]));
}
C'est juste au dessus de ma tête et n'a pas été testé ou comparé.
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;
}
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
Deux mashups qui plient les deux opérations de grignotage en une seule.
Version probablement assez efficace:
public static string ByteArrayToString2(byte[] ba)
{
char[] c = new char[ba.Length * 2];
for( int i = 0; i < ba.Length * 2; ++i)
{
byte b = (byte)((ba[i>>1] >> 4*((i&1)^1)) & 0xF);
c[i] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string( c );
}
Version décadente linq-with-bit-hacking:
public static string ByteArrayToString(byte[] ba)
{
return string.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) );
}
Et inverser:
public static byte[] HexStringToByteArray( string s )
{
byte[] ab = new byte[s.Length>>1];
for( int i = 0; i < s.Length; i++ )
{
int b = s[i];
b = (b - '0') + ((('9' - b)>>31)&-7);
ab[i>>1] |= (byte)(b << 4*((i&1)^1));
}
return ab;
}
Une autre façon consiste stackalloc
à réduire la pression de la mémoire GC:
static string ByteToHexBitFiddle(byte[] bytes)
{
var c = stackalloc char[bytes.Length * 2 + 1];
int b;
for (int i = 0; i < bytes.Length; ++i)
{
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
}
c[bytes.Length * 2 ] = '\0';
return new string(c);
}
Voici ma chance. J'ai créé une paire de classes d'extension pour étendre la chaîne et l'octet. Sur le test des fichiers volumineux, les performances sont comparables à la manipulation d'octets 2.
Le code ci-dessous pour ToHexString est une implémentation optimisée de l'algorithme de recherche et de décalage. Il est presque identique à celui de Behrooz, mais il s'avère utiliser un foreach
pour itérer et un compteur est plus rapide qu'une indexation explicitefor
.
Il vient en 2e position derrière Byte Manipulation 2 sur ma machine et est un code très lisible. Les résultats des tests suivants sont également intéressants:
ToHexStringCharArrayWithCharArrayLookup: 41 589,69 ticks moyens (plus de 1000 runs), 1,5X ToHexStringCharArrayWithStringLookup: 50 764,06 ticks moyens (plus de 1000 runs), 1,2X ToHexStringStringBuilderWithCharArrayLookup: 62 812,87 plus
Sur la base des résultats ci-dessus, il semble sûr de conclure que:
Voici le code:
using System;
namespace ConversionExtensions
{
public static class ByteArrayExtensions
{
private readonly static char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
public static string ToHexString(this byte[] bytes)
{
char[] hex = new char[bytes.Length * 2];
int index = 0;
foreach (byte b in bytes)
{
hex[index++] = digits[b >> 4];
hex[index++] = digits[b & 0x0F];
}
return new string(hex);
}
}
}
using System;
using System.IO;
namespace ConversionExtensions
{
public static class StringExtensions
{
public static byte[] ToBytes(this string hexString)
{
if (!string.IsNullOrEmpty(hexString) && hexString.Length % 2 != 0)
{
throw new FormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");
}
hexString = hexString.ToUpperInvariant();
byte[] data = new byte[hexString.Length / 2];
for (int index = 0; index < hexString.Length; index += 2)
{
int highDigitValue = hexString[index] <= '9' ? hexString[index] - '0' : hexString[index] - 'A' + 10;
int lowDigitValue = hexString[index + 1] <= '9' ? hexString[index + 1] - '0' : hexString[index + 1] - 'A' + 10;
if (highDigitValue < 0 || lowDigitValue < 0 || highDigitValue > 15 || lowDigitValue > 15)
{
throw new FormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");
}
else
{
byte value = (byte)((highDigitValue << 4) | (lowDigitValue & 0x0F));
data[index / 2] = value;
}
}
return data;
}
}
}
Voici les résultats des tests que j'ai obtenus lorsque j'ai mis mon code dans le projet de test de @ patridge sur ma machine. J'ai également ajouté un test pour convertir un tableau d'octets en hexadécimal. Les tests qui ont exercé mon code sont ByteArrayToHexViaOptimizedLookupAndShift et HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte a été extrait de XXXX. Le HexToByteArrayViaSoapHexBinary est celui de la réponse de @ Mykroft.
Processeur Intel Pentium III Xeon
Cores: 4 <br/> Current Clock Speed: 1576 <br/> Max Clock Speed: 3092 <br/>
Conversion d'un tableau d'octets en une représentation sous forme de chaîne hexadécimale
ByteArrayToHexViaByteManipulation2: 39366,64 ticks moyens (sur 1000 pistes), 22,4X
ByteArrayToHexViaOptimizedLookupAndShift: 41 588,64 ticks en moyenne (sur 1 000 exécutions), 21,2 fois
ByteArrayToHexViaLookup: 55 509,56 ticks en moyenne (sur 1000 pistes), 15,9 fois
ByteArrayToHexViaByteManipulation: 65 349,12 ticks moyens (sur 1000 pistes), 13,5 fois
ByteArrayToHexViaLookupAndShift: 86 926,87 ticks moyens (plus de 1 000 exécutions), 10,2 X
ByteArrayToHexStringViaBitConverter: 139353,73 ticks moyens (sur 1000 pistes), 6,3X
ByteArrayToHexViaSoapHexBinary: 314 598,77 ticks moyens (plus de 1 000 exécutions), 2,8 fois
ByteArrayToHexStringViaStringBuilderForEachByteToString: 344 264,63 ticks moyens (plus de 1000 exécutions), 2,6 X
ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382 623,44 ticks moyens (plus de 1 000 exécutions), 2,3 X
ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818 111,95 ticks en moyenne (sur 1000 exécutions), 1,1X
ByteArrayToHexStringViaStringConcatArrayConvertAll: 839 244,84 ticks en moyenne (sur 1000 pistes), 1,1X
ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867 303,98 ticks moyens (plus de 1000 exécutions), 1,0X
ByteArrayToHexStringViaStringJoinArrayConvertAll: 882 710,28 ticks moyens (plus de 1 000 exécutions), 1,0 X
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;
}