Comment pouvez-vous convertir un tableau d’octets en chaîne hexadécimale et vice versa?
Non plus:
public static ssortingng ByteArrayToSsortingng(byte[] ba) { SsortingngBuilder hex = new SsortingngBuilder(ba.Length * 2); foreach (byte b in ba) hex.AppendFormat("{0:x2}", b); return hex.ToSsortingng(); }
ou:
public static ssortingng ByteArrayToSsortingng(byte[] ba) { return BitConverter.ToSsortingng(ba).Replace("-",""); }
Il y a encore plus de variantes, par exemple ici .
La conversion inverse irait comme ceci:
public static byte[] SsortingngToByteArray(Ssortingng 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 Subssortingng
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 SubSsortingng
.
Note: nouveau leader à partir du 2015-08-20.
J’ai exécuté chacune des différentes méthodes de conversion en Stopwatch
des tests de performances bruts sur Stopwatch
, une exécution avec une phrase aléatoire (n = 61, 1000 itérations) et une parsing avec un texte de Project Gutenburg (n = 1 238 957, 150 itérations). Voici les résultats, du plus rapide au plus lent. Toutes les mesures sont en ticks ( 10 000 ticks = 1 ms ) et toutes les notes relatives sont comparées à l’implémentation de SsortingngBuilder
[la plus lente]. Pour le code utilisé, voir ci-dessous ou le référentiel d’infrastructure de test où je maintiens maintenant le code pour l’exécuter.
ATTENTION: Ne vous fiez pas à ces statistiques pour quelque chose de concret; ils sont simplement un échantillon de données d’échantillons. Si vous avez vraiment besoin de performances exceptionnelles, 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.
unsafe
(via CodesInChaos) (ajouté pour tester repo par airbreather )
BitConverter
(via Tomalak)
{SoapHexBinary}.ToSsortingng
(via Mykroft)
{byte}.ToSsortingng("X2")
(en utilisant foreach
) (dérivé de la réponse de Will Dean)
{byte}.ToSsortingng("X2")
(en utilisant {IEnumerable}.Aggregate
, nécessite System.Linq) (via Mark)
Array.ConvertAll
(en utilisant ssortingng.Join
) (via Will Dean)
Array.ConvertAll
(en utilisant ssortingng.Concat
, nécessite .NET 4.0) (via Will Dean)
{SsortingngBuilder}.AppendFormat
(utilisant foreach
) (via Tomalak)
{SsortingngBuilder}.AppendFormat
(utilisant {IEnumerable}.Aggregate
, nécessite System.Linq) (dérivé de la réponse de Tomalak)
Les tables de consultation ont pris la tête sur la manipulation d’octets. Fondamentalement, il existe une forme de précalculer ce que tout octet ou octet donné sera en hexadécimal. Ensuite, lorsque vous parcourez les données, il vous suffit de regarder la partie suivante pour voir quelle chaîne hexadécimale ce serait. Cette valeur est ensuite ajoutée à la sortie de chaîne résultante d’une certaine manière. Pendant longtemps, la manipulation par octets, potentiellement plus difficile à lire par certains développeurs, était l’approche la plus performante.
Votre meilleur pari est 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 méthode plus rapide mais consommant davantage 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 repo et à append vos propres méthodes. Veuillez soumettre une demande d’extrait si vous trouvez quelque chose d’intéressant ou souhaitez aider à améliorer le framework de test qu’il utilise.
Func
) à /Tests/ConvertByteArrayToHexSsortingng/Test.cs. TestCandidates
dans cette même classe. GenerateTestInput
dans cette même classe. static ssortingng ByteArrayToHexSsortingngViaSsortingngJoinArrayConvertAll(byte[] bytes) { return ssortingng.Join(ssortingng.Empty, Array.ConvertAll(bytes, b => b.ToSsortingng("X2"))); } static ssortingng ByteArrayToHexSsortingngViaSsortingngConcatArrayConvertAll(byte[] bytes) { return ssortingng.Concat(Array.ConvertAll(bytes, b => b.ToSsortingng("X2"))); } static ssortingng ByteArrayToHexSsortingngViaBitConverter(byte[] bytes) { ssortingng hex = BitConverter.ToSsortingng(bytes); return hex.Replace("-", ""); } static ssortingng ByteArrayToHexSsortingngViaSsortingngBuilderAggregateByteToSsortingng(byte[] bytes) { return bytes.Aggregate(new SsortingngBuilder(bytes.Length * 2), (sb, b) => sb.Append(b.ToSsortingng("X2"))).ToSsortingng(); } static ssortingng ByteArrayToHexSsortingngViaSsortingngBuilderForEachByteToSsortingng(byte[] bytes) { SsortingngBuilder hex = new SsortingngBuilder(bytes.Length * 2); foreach (byte b in bytes) hex.Append(b.ToSsortingng("X2")); return hex.ToSsortingng(); } static ssortingng ByteArrayToHexSsortingngViaSsortingngBuilderAggregateAppendFormat(byte[] bytes) { return bytes.Aggregate(new SsortingngBuilder(bytes.Length * 2), (sb, b) => sb.AppendFormat("{0:X2}", b)).ToSsortingng(); } static ssortingng ByteArrayToHexSsortingngViaSsortingngBuilderForEachAppendFormat(byte[] bytes) { SsortingngBuilder hex = new SsortingngBuilder(bytes.Length * 2); foreach (byte b in bytes) hex.AppendFormat("{0:X2}", b); return hex.ToSsortingng(); } static ssortingng 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 ssortingng(c); } static ssortingng 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 ssortingng(c); } static ssortingng ByteArrayToHexViaSoapHexBinary(byte[] bytes) { SoapHexBinary soapHexBinary = new SoapHexBinary(bytes); return soapHexBinary.ToSsortingng(); } static ssortingng ByteArrayToHexViaLookupAndShift(byte[] bytes) { SsortingngBuilder result = new SsortingngBuilder(bytes.Length * 2); ssortingng hexAlphabet = "0123456789ABCDEF"; foreach (byte b in bytes) { result.Append(hexAlphabet[(int)(b >> 4)]); result.Append(hexAlphabet[(int)(b & 0xF)]); } return result.ToSsortingng(); } static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_Lookup32, GCHandleType.Pinned).AddrOfPinnedObject(); static ssortingng ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes) { var lookupP = _lookup32UnsafeP; var result = new ssortingng((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 => { ssortingng s = i.ToSsortingng("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 ssortingng(result); } static ssortingng ByteArrayToHexViaLookup(byte[] bytes) { ssortingng[] hexSsortingngTable = new ssortingng[] { "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", }; SsortingngBuilder result = new SsortingngBuilder(bytes.Length * 2); foreach (byte b in bytes) { result.Append(hexSsortingngTable[b]); } return result.ToSsortingng(); }
Ajout de la réponse de Waleed à l’parsing. Tres rapide.
Ajout de la variante ssortingng.Concat
Array.ConvertAll
pour être complet (nécessite .NET 4.0). À égalité avec ssortingng.Join
version.
Le référentiel de test inclut d’autres variantes telles que SsortingngBuilder.Append(b.ToSsortingng("X2"))
. Aucun n’a bouleversé les résultats. foreach
est plus rapide que {IEnumerable}.Aggregate
, par exemple, mais BitConverter
gagne toujours.
Ajout de la réponse SoapHexBinary de SoapHexBinary
à l’parsing, qui a pris la troisième place.
Ajout de la réponse de manipulation des octets de CodesInChaos, qui a pris la première place (avec une grande marge sur les grands blocs de texte).
Ajout de la réponse de recherche de Nathan Moinvaziri et de la variante du blog de Brian Lambert. Les deux plutôt rapides, mais ne prenant pas la tête sur la machine de test que j’ai utilisée (AMD Phenom 9750).
Ajout de la nouvelle réponse de recherche basée sur l’octet @ CodesInChaos. Il semble avoir pris la tête à la fois des tests de phrases et des tests en texte intégral.
Ajout des optimisations airbreather et de la variante unsafe
au repository de cette réponse . Si vous souhaitez jouer dans un jeu dangereux, vous pouvez obtenir des gains de performance considérables par rapport aux grands gagnants précédents sur les textes courts et les grands textes.
Il y a une classe appelée SoapHexBinary qui fait exactement ce que vous voulez.
using System.Runtime.Remoting.Metadata.W3cXsd2001; public static byte[] GetSsortingngToBytes(ssortingng value) { SoapHexBinary shb = SoapHexBinary.Parse(value); return shb.Value; } public static ssortingng GetBytesToSsortingng(byte[] value) { SoapHexBinary shb = new SoapHexBinary(value); return shb.ToSsortingng(); }
Lors de l’écriture du code cryptographique, il est courant d’éviter les twigs dépendantes des données et les recherches de tables pour garantir que le runtime ne dépend pas des données, car le timing dépendant des données peut entraîner des attaques par canal secondaire.
C’est aussi assez rapide.
static ssortingng 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 ssortingng(c); }
Ph’nglui mglw’nafh Cthulhu R’lyeh wgah’nagl fhtagn
Abandonnez tout espoir, vous qui entrez ici
Une explication du bidouillage bizarre:
bytes[i] >> 4
extrait le haut quartet d’un octet bytes[i] & 0xF
extrait le faible quartet d’un octet b - 10
< 0
pour les valeurs b < 10
, qui deviendront un chiffre décimal >= 0
pour les valeurs b > 10
, qui deviendra une lettre de A
à F
i >> 31
sur un entier de 32 bits signé extrait le signe, grâce à l’extension de signe. Il 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 compris entre 10 et 15. Nous voulons le mapper à A
(65) à F
(70), ce qui implique l’ajout de 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
puisque (0 & -7) == 0
et (-1 & -7) == -7
. Quelques considérations supplémentaires:
c
, car la mesure montre que le calculer à partir de i
est moins cher. i < bytes.Length
comme limite supérieure de la boucle permet à JITter d'éliminer les vérifications de limites sur les bytes[i]
, j'ai donc choisi cette variante. b
int permet des conversions inutiles de et vers des octets. Si vous voulez plus de flexibilité que BitConverter
, mais que vous ne voulez pas ces boucles explicites du style des années 1990, vous pouvez:
Ssortingng.Join(Ssortingng.Empty, Array.ConvertAll(bytes, x => x.ToSsortingng("X2")));
Ou, si vous utilisez .NET 4.0:
Ssortingng.Concat(Array.ConvertAll(bytes, x => x.ToSsortingng("X2")));
(Ce dernier à partir d’un commentaire sur le post original.)
Vous pouvez utiliser la méthode BitConverter.ToSsortingng:
byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256} Console.WriteLine( BitConverter.ToSsortingng(bytes));
Sortie:
00-01-02-04-08-10-20-40-80-FF
Plus d’informations: Méthode BitConverter.ToSsortingng (Byte [])
Une autre approche basée sur une table de consultation. Celui-ci utilise une seule table de recherche pour chaque octet, au lieu d’une table de consultation 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 ssortingng(result); }
J’ai également testé des variantes de cette méthode en utilisant ushort
, struct{char X1, X2}
, struct{byte X1, X2}
dans la table de consultation.
Selon la cible de compilation (x86, X64), celles-ci avaient les mêmes performances ou étaient légèrement plus lentes que cette variante.
Et pour des performances encore plus élevées, ses frères et sœurs unsafe
:
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 acceptable d'écrire directement dans la chaîne:
public static ssortingng ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes) { var lookupP = _lookup32UnsafeP; var result = new ssortingng((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; }
Je viens de rencontrer le même problème aujourd’hui, et je suis tombé sur ce code:
private static ssortingng 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 ssortingng(c); }
Source: Forum post byte [] Array to Hex Ssortingng (voir l’article de PZahra). J’ai un peu modifié le code pour supprimer le préfixe 0x.
J’ai fait des tests de performance sur le code et c’était presque huit fois plus rapide que d’utiliser BitConverter.ToSsortingng () (le plus rapide selon la publication de pasortingdge).
Ce problème pourrait également être résolu en utilisant 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:
Ma solution utilise 1024 octets pour la table d’encodage 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]]; }
SsortingngBuilderToSsortingngFromBytes: 106148 BitConverterToSsortingngFromBytes: 15783 ArrayConvertAllToSsortingngFromBytes: 54290 ByteManipulationToCharArray: 8444 TableBasedToCharArray: 5651 *
* cette solution
Lors du décodage, IOException et IndexOutOfRangeException peuvent se produire (si un caractère a une valeur trop élevée> 256). Des méthodes de de / encodage des stream ou des tableaux doivent être implémentées, 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 aux éditions ultérieures).
Je vais faire valoir que cette modification est erronée et expliquer pourquoi elle pourrait être annulée. En cours de route, vous apprendrez peut-être une ou deux choses sur certains éléments internes et vous verrez un autre exemple de ce qu’est réellement l’optimisation prématurée et de la façon dont elle peut vous mordre.
tl; dr: Utilisez simplement Convert.ToByte
et Ssortingng.Subssortingng
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 n’utilise pas Convert.ToByte
si vous avez besoin de performances. N’utilisez rien d’autre que Ssortingng.Subssortingng
en combinaison avec Convert.ToByte
, sauf si quelqu’un a quelque chose d’intéressant à dire à ce sujet dans les commentaires de cette réponse.
attention: cette réponse peut devenir obsolète si une Convert.ToByte(char[], Int32)
est implémentée dans la structure. Cela ne devrait pas arriver bientôt.
En règle générale, je n’aime pas trop dire “ne pas optimiser prématurément”, car personne ne sait quand “prématuré” l’est. La seule chose que vous devez prendre en compte lorsque vous décidez d’optimiser ou non est la suivante: “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 soit plus mature ou que vous ayez besoin de la performance (s’il y a un réel besoin, vous ferez le temps). En attendant, faites la chose la plus simple qui pourrait éventuellement fonctionner.
Code d’origine:
public static byte[] HexadecimalSsortingngToByteArray_Original(ssortingng 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[] HexadecimalSsortingngToByteArray_Rev4(ssortingng input) { var outputLength = input.Length / 2; var output = new byte[outputLength]; using (var sr = new SsortingngReader(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 Ssortingng.Subssortingng
et utilise à la place un SsortingngReader
. La raison donnée est:
Edit: vous pouvez améliorer les performances des chaînes longues en utilisant un parsingur en un seul passage, comme ceci:
Eh bien, en regardant le code de référence pour Ssortingng.Subssortingng
, c'est clairement "single-pass"; et pourquoi ça ne devrait pas être? Il fonctionne au niveau octet, pas sur des paires de substitution.
Il alloue cependant une nouvelle chaîne, mais vous devez alors en allouer une pour passer à Convert.ToByte
. En outre, la solution fournie dans la révision alloue un autre object à chaque itération (le tableau à deux caractères); vous pouvez en toute sécurité placer cette allocation en dehors de la boucle et réutiliser le tableau pour éviter cela.
public static byte[] HexadecimalSsortingngToByteArray(ssortingng input) { var outputLength = input.Length / 2; var output = new byte[outputLength]; var numeral = new char[2]; using (var sr = new SsortingngReader(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; }
Each hexadecimal numeral
represents a single octet using two digits (symbols).
But then, why call SsortingngReader.Read
twice? Just call its second overload and ask it to read two characters in the two-char array at once; and reduce the amount of calls by two.
public static byte[] HexadecimalSsortingngToByteArray(ssortingng input) { var outputLength = input.Length / 2; var output = new byte[outputLength]; var numeral = new char[2]; using (var sr = new SsortingngReader(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; }
What you're left with is a ssortingng reader whose only added "value" is a parallel index (internal _pos
) which you could have declared yourself (as j
for example), a redundant length variable (internal _length
), and a redundant reference to the input ssortingng (internal _s
). In other words, it's useless.
If you wonder how Read
"reads", just look at the code , all it does is call Ssortingng.CopyTo
on the input ssortingng. The rest is just book-keeping overhead to maintain values we don't need.
So, remove the ssortingng reader already, and call CopyTo
yourself; it's simpler, clearer, and more efficient.
public static byte[] HexadecimalSsortingngToByteArray(ssortingng 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; }
Do you really need a j
index that increments in steps of two parallel to i
? Of course not, just multiply i
by two (which the comstackr should be able to optimize to an addition).
public static byte[] HexadecimalSsortingngToByteArray_BestEffort(ssortingng 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; }
What does the solution look like now? Exactly like it was at the beginning, only instead of using Ssortingng.Subssortingng
to allocate the ssortingng and copy the data to it, you're using an intermediary array to which you copy the hexadecimal numerals to, then allocate the ssortingng yourself and copy the data again from the array and into the ssortingng (when you pass it in the ssortingng constructor). The second copy might be optimized-out if the ssortingng is already in the intern pool, but then Ssortingng.Subssortingng
will also be able to avoid it in these cases.
In fact, if you look at Ssortingng.Subssortingng
again, you see that it uses some low-level internal knowledge of how ssortingngs are constructed to allocate the ssortingng faster than you could normally do it, and it inlines the same code used by CopyTo
directly in there to avoid the call overhead.
Ssortingng.Subssortingng
Manual method
Conclusion? If you want to use Convert.ToByte(Ssortingng, Int32)
(because you don't want to re-implement that functionality yourself), there doesn't seem to be a way to beat Ssortingng.Subssortingng
; all you do is run in circles, re-inventing the wheel (only with sub-optimal materials).
Note that using Convert.ToByte
and Ssortingng.Subssortingng
is a perfectly valid choice if you don't need extreme performance. Remember: only opt for an alternative if you have the time and resources to investigate how it works properly.
If there was a Convert.ToByte(char[], Int32)
, things would be different of course (it would be possible to do what I described above and completely avoid Ssortingng
).
I suspect that people who report better performance by "avoiding Ssortingng.Subssortingng
" also avoid Convert.ToByte(Ssortingng, Int32)
, which you should really be doing if you need the performance anyway. Look at the countless other answers to discover all the different approaches to do that.
Disclaimer: I haven't decomstackd the latest version of the framework to verify that the reference source is up-to-date, I assume it is.
Now, it all sounds good and logical, hopefully even obvious if you've managed to get so far. But is it true?
Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz Cores: 8 Current Clock Speed: 2600 Max Clock Speed: 2600 -------------------- Parsing hexadecimal ssortingng into an array of bytes -------------------- HexadecimalSsortingngToByteArray_Original: 7,777.09 average ticks (over 10000 runs), 1.2X HexadecimalSsortingngToByteArray_BestEffort: 8,550.82 average ticks (over 10000 runs), 1.1X HexadecimalSsortingngToByteArray_Rev4: 9,218.03 average ticks (over 10000 runs), 1.0X
Oui!
Props to Parsortingdge for the bench framework, it's easy to hack. The input used is the following SHA-1 hash repeated 5000 times to make a 100,000 bytes long ssortingng.
209113288F93A9AB8E474EA78D899AFDBB874355
S'amuser! (But optimize with moderation.)
Complement to answer by @CodesInChaos (reversed method)
public static byte[] HexToByteUsingByteManipulation(ssortingng 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
is to support also lower case letters
hi = hi + 10 + ((hi >> 31) & 7);
is the same as:
hi = ch-65 + 10 + (((ch-65) >> 31) & 7);
For '0'..'9' it is the same as hi = ch - 65 + 10 + 7;
which is hi = ch - 48
(this is because of 0xffffffff & 7
).
For 'A'..'F' it is hi = ch - 65 + 10;
(this is because of 0x00000000 & 7
).
For 'a'..'f' we have to big numbers so we must subtract 32 from default version by making some bits 0
by using & 0x0f
.
65 is code for 'A'
48 is code for '0'
7 is the number of letters between '9'
and 'A'
in the ASCII table ( ...456789:;<=>?@ABCD...
).
This is a great post. I like Waleed’s solution. I haven’t run it through pasortingdge’s test but it seems to be quite fast. I also needed the reverse process, converting a hex ssortingng to a byte array, so I wrote it as a reversal of Waleed’s solution. Not sure if it’s any faster than Tomalak’s original solution. Again, I did not run the reverse process through pasortingdge’s test either.
private byte[] HexSsortingngToByteArray(ssortingng hexSsortingng) { int hexSsortingngLength = hexSsortingng.Length; byte[] b = new byte[hexSsortingngLength / 2]; for (int i = 0; i < hexStringLength; i += 2) { int topChar = (hexString[i] > 0x40 ? hexSsortingng[i] - 0x37 : hexSsortingng[i] - 0x30) << 4; int bottomChar = hexString[i + 1] > 0x40 ? hexSsortingng[i + 1] - 0x37 : hexSsortingng[i + 1] - 0x30; b[i / 2] = Convert.ToByte(topChar + bottomChar); } return b; }
Why make it complex? This is simple in Visual Studio 2008:
C#:
ssortingng hex = BitConverter.ToSsortingng(YourByteArray).Replace("-", "");
VB:
Dim hex As Ssortingng = BitConverter.ToSsortingng(YourByteArray).Replace("-", "")
Not to stack on to the many answers here, but I found a fairly optimal (~4.5x better than accepted), straightforward implementation of the hex ssortingng parser. First, output from my tests (the first batch is my implementation):
Give me that ssortingng: 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: (SsortingngToByteArray) 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
The base64 and ‘BitConverter’d’ lines are there to test for correctness. Note that they are equal.
The implementation:
public static byte[] ToByteArrayFromHex(ssortingng hexSsortingng) { if (hexSsortingng.Length % 2 != 0) throw new ArgumentException("Ssortingng must have an even length"); var array = new byte[hexSsortingng.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; }
I sortinged some stuff with unsafe
and moving the (clearly redundant) character-to-nibble if
sequence to another method, but this was the fastest it got.
(I concede that this answers half the question. I felt that the ssortingng->byte[] conversion was underrepresented, while the byte[]->ssortingng angle seems to be well covered. Thus, this answer.)
Safe versions:
public static class HexHelper { [System.Diagnostics.Contracts.Pure] public static ssortingng ToHex(this byte[] value) { if (value == null) throw new ArgumentNullException("value"); const ssortingng 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 ssortingng(chars); } [System.Diagnostics.Contracts.Pure] public static byte[] FromHex(this ssortingng 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; } } }
Unsafe versions For those who prefer performance and do not afraid of unsafeness. About 35% faster ToHex and 10% faster FromHex.
public static class HexUnsafeHelper { [System.Diagnostics.Contracts.Pure] public static unsafe ssortingng ToHex(this byte[] value) { if (value == null) throw new ArgumentNullException("value"); const ssortingng alphabet = @"0123456789ABCDEF"; ssortingng result = new ssortingng(' ', 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 ssortingng 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 For benchmark testing initializing alphabet every time convert function called is wrong, alphabet must be const (for ssortingng) or static readonly (for char[]). Then alphabet-based conversion of byte[] to ssortingng becomes as fast as byte manipulation versions.
And of course test must be comstackd in Release (with optimization) and with debug option “Suppress JIT optimization” turned off (same for “Enable Just My Code” if code must be debuggable).
Inverse function for Waleed Eissa code (Hex Ssortingng To Byte Array):
public static byte[] HexToBytes(this ssortingng hexSsortingng) { byte[] b = new byte[hexSsortingng.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; }
Waleed Eissa function with lower case support:
public static ssortingng 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 ssortingng(c); }
Extension methods (disclaimer: completely untested code, BTW…):
public static class ByteExtensions { public static ssortingng ToHexSsortingng(this byte[] ba) { SsortingngBuilder hex = new SsortingngBuilder(ba.Length * 2); foreach (byte b in ba) { hex.AppendFormat("{0:x2}", b); } return hex.ToSsortingng(); } }
etc.. Use either of Tomalak’s three solutions (with the last one being an extension method on a ssortingng).
From Microsoft’s developers, a nice, simple conversion:
public static ssortingng ByteArrayToSsortingng(byte[] ba) { // Concatenate the bytes into one long ssortingng return ba.Aggregate(new SsortingngBuilder(32), (sb, b) => sb.Append(b.ToSsortingng("X2")) ).ToSsortingng(); }
While the above is clean an compact, performance junkies will scream about it using enumerators. You can get peak performance with an improved version of Tomolak’s original answer:
public static ssortingng ByteArrayToSsortingng(byte[] ba) { SsortingngBuilder hex = new SsortingngBuilder(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(); }
This is the fastest of all the routines I've seen posted here so far. Don't just take my word for it... performance test each routine and inspect its CIL code for yourself.
In terms of speed, this seems to be better than anything here:
public static ssortingng ToHexSsortingng(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 ssortingng(r); }
I did not get the code you suggested to work, Olipro. hex[i] + hex[i+1]
apparently returned an int
.
I did, however have some success by taking some hints from Waleeds code and hammering this together. It’s ugly as hell but it seems to work and performs at 1/3 of the time compared to the others according to my tests (using pasortingdges testing mechanism). Depending on input size. Switching around the ?:s to separate out 0-9 first would probably yield a slightly faster result since there are more numbers than letters.
public static byte[] SsortingngToByteArray2(ssortingng 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; }
This version of ByteArrayToHexViaByteManipulation could be faster.
From my reports:
…
static private readonly char[] hexAlphabet = new char[] {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; static ssortingng 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 ssortingng(c); }
And I think this one is an optimization:
static private readonly char[] hexAlphabet = new char[] {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; static ssortingng 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 ssortingng(c); }
I’ll enter this bit fiddling competition as I have an answer that also uses bit-fiddling to decode hexadecimals. Note that using character arrays may be even faster as calling SsortingngBuilder
methods will take time as well.
public static Ssortingng ToHex (byte[] data) { int dataLength = data.Length; // pre-create the ssortingngbuilder using the length of the data * 2, precisely enough SsortingngBuilder sb = new SsortingngBuilder (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.ToSsortingng (); } public static byte[] FromHex (Ssortingng 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; }
Converted from Java code.
And for inserting into an SQL ssortingng (if you’re not using command parameters):
public static Ssortingng ByteArrayToSQLHexSsortingng(byte[] Source) { return = "0x" + BitConverter.ToSsortingng(Source).Replace("-", ""); }
Yet another variation for diversity:
public static byte[] FromHexSsortingng(ssortingng src) { if (Ssortingng.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; }
Not optimized for speed, but more LINQy than most answers (.NET 4.0):
Public Function FromHexToByteArray(hex As Ssortingng) As Byte() hex = If(hex, Ssortingng.Empty) If hex.Length Mod 2 = 1 Then hex = "0" & hex Return Enumerable.Range(0, hex.Length \ 2).Select(Function(i) Convert.ToByte(hex.Subssortingng(i * 2, 2), 16)).ToArray End Function Public Function ToHexSsortingng(bytes As IEnumerable(Of Byte)) As Ssortingng Return Ssortingng.Concat(bytes.Select(Function(b) b.ToSsortingng("X2"))) End Function
Two mashups which folds the two nibble operations into one.
Probably pretty efficient version:
public static ssortingng ByteArrayToSsortingng2(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 ssortingng( c ); }
Decadent linq-with-bit-hacking version:
public static ssortingng ByteArrayToSsortingng(byte[] ba) { return ssortingng.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) ); }
And reverse:
public static byte[] HexSsortingngToByteArray( ssortingng 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; }
Another way is by using stackalloc
to reduce GC memory pressure:
static ssortingng 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 ssortingng(c); }
Here’s my shot at it. I’ve created a pair of extension classes to extend ssortingng and byte. On the large file test, the performance is comparable to Byte Manipulation 2.
The code below for ToHexSsortingng is an optimized implementation of the lookup and shift algorithm. It is almost identical to the one by Behrooz, but it turns out using a foreach
to iterate and a counter is faster than an explicitly indexing for
.
It comes in 2nd place behind Byte Manipulation 2 on my machine and is very readable code. The following test results are also of interest:
ToHexSsortingngCharArrayWithCharArrayLookup: 41,589.69 average ticks (over 1000 runs), 1.5X ToHexSsortingngCharArrayWithSsortingngLookup: 50,764.06 average ticks (over 1000 runs), 1.2X ToHexSsortingngSsortingngBuilderWithCharArrayLookup: 62,812.87 average ticks (over 1000 runs), 1.0X
Based on the above results it seems safe to conclude that:
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 ssortingng ToHexSsortingng(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 ssortingng(hex); } } } using System; using System.IO; namespace ConversionExtensions { public static class SsortingngExtensions { public static byte[] ToBytes(this ssortingng hexSsortingng) { if (!ssortingng.IsNullOrEmpty(hexSsortingng) && hexSsortingng.Length % 2 != 0) { throw new FormatException("Hexadecimal ssortingng must not be empty and must contain an even number of digits to be valid."); } hexSsortingng = hexSsortingng.ToUpperInvariant(); byte[] data = new byte[hexSsortingng.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 AF."); } else { byte value = (byte)((highDigitValue << 4) | (lowDigitValue & 0x0F)); data[index / 2] = value; } } return data; } } }
Below are the test results that I got when I put my code in @pasortingdge's testing project on my machine. I also added a test for converting to a byte array from hexadecimal. The test runs that exercised my code are ByteArrayToHexViaOptimizedLookupAndShift and HexToByteArrayViaByteManipulation. The HexToByteArrayViaConvertToByte was taken from XXXX. The HexToByteArrayViaSoapHexBinary is the one from @Mykroft's answer.
Intel Pentium III Xeon processor
Cores: 4
Current Clock Speed: 1576
Max Clock Speed: 3092
Converting array of bytes into hexadecimal ssortingng representation
ByteArrayToHexViaByteManipulation2: 39,366.64 average ticks (over 1000 runs), 22.4X
ByteArrayToHexViaOptimizedLookupAndShift: 41,588.64 average ticks (over 1000 runs), 21.2X
ByteArrayToHexViaLookup: 55,509.56 average ticks (over 1000 runs), 15.9X
ByteArrayToHexViaByteManipulation: 65,349.12 average ticks (over 1000 runs), 13.5X
ByteArrayToHexViaLookupAndShift: 86,926.87 average ticks (over 1000 runs), 10.2X
ByteArrayToHexSsortingngViaBitConverter: 139,353.73 average ticks (over 1000 runs),6.3X
ByteArrayToHexViaSoapHexBinary: 314,598.77 average ticks (over 1000 runs), 2.8X
ByteArrayToHexSsortingngViaSsortingngBuilderForEachByteToSsortingng: 344,264.63 average ticks (over 1000 runs), 2.6X
ByteArrayToHexSsortingngViaSsortingngBuilderAggregateByteToSsortingng: 382,623.44 average ticks (over 1000 runs), 2.3X
ByteArrayToHexSsortingngViaSsortingngBuilderForEachAppendFormat: 818,111.95 average ticks (over 1000 runs), 1.1X
ByteArrayToHexSsortingngViaSsortingngConcatArrayConvertAll: 839,244.84 average ticks (over 1000 runs), 1.1X
ByteArrayToHexSsortingngViaSsortingngBuilderAggregateAppendFormat: 867,303.98 average ticks (over 1000 runs), 1.0X
ByteArrayToHexSsortingngViaSsortingngJoinArrayConvertAll: 882,710.28 average ticks (over 1000 runs), 1.0X
Another fast function…
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[] HexSsortingngToByteArray( ssortingng 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; }
For performance I would go with drphrozens solution. A tiny optimization for the decoder could be to use a table for either char to get rid of the “<< 4".
Clearly the two method calls are costly. If some kind of check is made either on input or output data (could be CRC, checksum or whatever) the if (b == 255)...
could be skipped and thereby also the method calls altogether.
Using offset++
and offset
instead of offset
and offset + 1
might give some theoretical benefit but I suspect the comstackr handles this better than me.
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])); }
This is just off the top of my head and has not been tested or benchmarked.