La réponse acceptée ci-dessous semble allouer une quantité horrible de chaînes dans la conversion de chaîne en octets. Je me demande comment cela affecte les performances
Wim Coenen
9
La classe SoapHexBinary fait exactement ce que vous voulez, je pense.
Mykroft
Il me semble que poser 2 questions dans 1 poste n'est pas tout à fait standard.
SandRock
Réponses:
1353
Soit:
publicstaticstringByteArrayToString(byte[] ba){StringBuilder hex =newStringBuilder(ba.Length*2);foreach(byte b in ba)
hex.AppendFormat("{0:x2}", b);return hex.ToString();}
Il y a encore plus de variantes de le faire, par exemple ici .
La conversion inverse se passerait comme ceci:
publicstaticbyte[]StringToByteArray(String hex){intNumberChars= hex.Length;byte[] bytes =newbyte[NumberChars/2];for(int i =0; i <NumberChars; i +=2)
bytes[i /2]=Convert.ToByte(hex.Substring(i,2),16);return bytes;}
L'utilisation Substringest 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.ToByteavant de pouvoir abandonner SubString.
Vous utilisez SubString. Cette boucle n'alloue-t-elle pas une quantité horrible d'objets chaîne?
Wim Coenen
30
Honnêtement - jusqu'à ce qu'il réduise considérablement les performances, j'aurais tendance à ignorer cela et à faire confiance au Runtime et au GC pour s'en occuper.
Tomalak
87
Étant donné qu'un octet est composé de deux quartets, toute chaîne hexadécimale qui représente valablement un tableau d'octets doit avoir un nombre de caractères pair. Un 0 ne doit être ajouté nulle part - en ajouter un reviendrait à faire l'hypothèse de données non valides potentiellement dangereuses. Si quelque chose, la méthode StringToByteArray doit lever une exception FormatException si la chaîne hexadécimale contient un nombre impair de caractères.
David Boike
7
@ 00jt Vous devez faire l'hypothèse que F == 0F. Soit c'est la même chose que 0F, soit l'entrée a été écrêtée et F est en fait le début de quelque chose que vous n'avez pas reçu. C'est à votre contexte de faire ces hypothèses, mais je crois qu'une fonction à usage général devrait rejeter les caractères impairs comme invalides au lieu de faire cette hypothèse pour le code appelant.
David Boike
11
@DavidBoike La question n'avait RIEN à voir avec "comment gérer les valeurs de flux éventuellement écrêtées". Il s'agit d'une chaîne. String myValue = 10.ToString ("X"); maValeur est "A" et non "0A". Maintenant, allez lire cette chaîne en octets, oups vous l'avez cassée.
00jt
488
Analyse de performance
Remarque: nouveau leader au 20/08/2015.
J'ai exécuté chacune des différentes méthodes de conversion à travers des Stopwatchtests 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' StringBuilderimplé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
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.
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.
Code de test
N'hésitez pas à jouer avec le code de test que j'ai utilisé. Une version est incluse ici, mais n'hésitez pas à cloner le 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.
Ajoutez la nouvelle méthode statique ( Func<byte[], string>) à /Tests/ConvertByteArrayToHexString/Test.cs.
Ajoutez le nom de cette méthode à la TestCandidatesvaleur de retour dans cette même classe.
Assurez-vous que vous exécutez la version d'entrée souhaitée, phrase ou texte, en basculant les commentaires GenerateTestInputdans cette même classe.
Appuyez F5et attendez la sortie (un vidage HTML est également généré dans le dossier / bin).
staticstringByteArrayToHexStringViaStringJoinArrayConvertAll(byte[] bytes){returnstring.Join(string.Empty,Array.ConvertAll(bytes, b => b.ToString("X2")));}staticstringByteArrayToHexStringViaStringConcatArrayConvertAll(byte[] bytes){returnstring.Concat(Array.ConvertAll(bytes, b => b.ToString("X2")));}staticstringByteArrayToHexStringViaBitConverter(byte[] bytes){string hex =BitConverter.ToString(bytes);return hex.Replace("-","");}staticstringByteArrayToHexStringViaStringBuilderAggregateByteToString(byte[] bytes){return bytes.Aggregate(newStringBuilder(bytes.Length*2),(sb, b)=> sb.Append(b.ToString("X2"))).ToString();}staticstringByteArrayToHexStringViaStringBuilderForEachByteToString(byte[] bytes){StringBuilder hex =newStringBuilder(bytes.Length*2);foreach(byte b in bytes)
hex.Append(b.ToString("X2"));return hex.ToString();}staticstringByteArrayToHexStringViaStringBuilderAggregateAppendFormat(byte[] bytes){return bytes.Aggregate(newStringBuilder(bytes.Length*2),(sb, b)=> sb.AppendFormat("{0:X2}", b)).ToString();}staticstringByteArrayToHexStringViaStringBuilderForEachAppendFormat(byte[] bytes){StringBuilder hex =newStringBuilder(bytes.Length*2);foreach(byte b in bytes)
hex.AppendFormat("{0:X2}", b);return hex.ToString();}staticstringByteArrayToHexViaByteManipulation(byte[] bytes){char[] c =newchar[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);}returnnewstring(c);}staticstringByteArrayToHexViaByteManipulation2(byte[] bytes){char[] c =newchar[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));}returnnewstring(c);}staticstringByteArrayToHexViaSoapHexBinary(byte[] bytes){SoapHexBinary soapHexBinary =newSoapHexBinary(bytes);return soapHexBinary.ToString();}staticstringByteArrayToHexViaLookupAndShift(byte[] bytes){StringBuilder result =newStringBuilder(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();}staticreadonlyuint* _lookup32UnsafeP =(uint*)GCHandle.Alloc(_Lookup32,GCHandleType.Pinned).AddrOfPinnedObject();staticstringByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes){var lookupP = _lookup32UnsafeP;var result =newstring((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;}staticuint[]_Lookup32=Enumerable.Range(0,255).Select(i =>{string s = i.ToString("X2");return((uint)s[0])+((uint)s[1]<<16);}).ToArray();staticstringByteArrayToHexViaLookupPerByte(byte[] bytes){var result =newchar[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);}returnnewstring(result);}staticstringByteArrayToHexViaLookup(byte[] bytes){string[] hexStringTable =newstring[]{"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 =newStringBuilder(bytes.Length*2);foreach(byte b in bytes){
result.Append(hexStringTable[b]);}return result.ToString();}
Mise à jour (2010-01-13)
Ajout de la réponse de Waleed à l'analyse. Tres rapide.
Mise à jour (2011-10-05)
Ajout d'une string.ConcatArray.ConvertAllvariante pour être complet (nécessite .NET 4.0). Au même niveau que la string.Joinversion.
Mise à jour (2012-02-05)
Le repo de test comprend plus de variantes telles que StringBuilder.Append(b.ToString("X2")). Aucun n'a bouleversé les résultats. foreachest plus rapide que {IEnumerable}.Aggregate, par exemple, mais BitConvertergagne toujours.
Mise à jour (2012-04-03)
Ajout de la SoapHexBinaryréponse de Mykroft à l'analyse, qui a pris la troisième place.
Mise à jour (2013-01-15)
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).
Mise à jour (2013-05-23)
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).
Mise à jour (2014-07-31)
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.
Mise à jour (2015-08-20)
Ajout des optimisations et de la unsafevariante 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.
Malgré la mise à disposition du code pour que vous puissiez faire la chose que vous avez demandée par vous-même, j'ai mis à jour le code de test pour inclure la réponse de Waleed. Mis à part tout grincheux, c'est beaucoup plus rapide.
patridge
2
@CodesInChaos Done. Et il a également gagné dans mes tests. Je ne prétends pas encore comprendre pleinement l'une des meilleures méthodes, mais elles sont facilement cachées de l'interaction directe.
patridge
6
Cette réponse n'a pas l'intention de répondre à la question de ce qui est «naturel» ou banal. L'objectif est de donner aux gens des repères de performance de base car, lorsque vous avez besoin de faire ces conversions, vous avez tendance à les faire beaucoup. Si quelqu'un a besoin d'une vitesse brute, il exécute simplement les tests de performances avec des données de test appropriées dans l'environnement informatique souhaité. Ensuite, glissez cette méthode dans une méthode d'extension où vous ne regarderez plus jamais son implémentation (par exemple, bytes.ToHexStringAtLudicrousSpeed()).
patridge
2
Je viens de produire une implémentation basée sur une table de recherche haute performance. Sa variante sûre est environ 30% plus rapide que le leader actuel sur mon CPU. Les variantes dangereuses sont encore plus rapides. stackoverflow.com/a/24343727/445517
CodesInChaos
244
Il existe une classe appelée SoapHexBinary qui fait exactement ce que vous voulez.
using System.Runtime.Remoting.Metadata.W3cXsd2001;publicstaticbyte[]GetStringToBytes(stringvalue){SoapHexBinary shb =SoapHexBinary.Parse(value);return shb.Value;}publicstaticstringGetBytesToString(byte[]value){SoapHexBinary shb =newSoapHexBinary(value);return shb.ToString();}
SoapHexBinary est disponible à partir de .NET 1.0 et se trouve dans mscorlib. Malgré son espace de noms drôle, il fait exactement ce que la question a posé.
Sly Gryphon
4
Super trouvaille! Notez que vous devrez remplir les chaînes impaires avec un 0 de début pour GetStringToBytes, comme l'autre solution.
Carter Medlin
Avez-vous vu la pensée de la mise en œuvre? La réponse acceptée a une meilleure IMHO.
SoapHexBinary n'est pas pris en charge dans .NET Core / .NET Standard ...
juFo
141
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.
staticstringByteToHexBitFiddle(byte[] bytes){char[] c =newchar[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));}returnnewstring(c);}
bytes[i] >> 4extrait le quartet haut d'un octet bytes[i] & 0xFextrait le quartet bas d'un octet
b - 10
est < 0pour les valeurs b < 10, qui deviendra un chiffre décimal
est >= 0pour les valeurs b > 10, qui deviendra une lettre de Aà F.
L'utilisation i >> 31sur un entier signé de 32 bits extrait le signe, grâce à l'extension de signe. Ce sera -1pour i < 0et 0pour i >= 0.
La combinaison de 2) et 3) montre que ce (b-10)>>31sera 0pour les lettres et -1pour les chiffres.
En examinant la casse des lettres, la dernière sommation devient 0et se bsitue dans la plage de 10 à 15. Nous voulons la mapper sur A(65) à F(70), ce qui implique d'ajouter 55 ( 'A'-10).
En examinant le cas des chiffres, nous voulons adapter le dernier sommet afin qu'il mappe bde la plage 0 à 9 à la plage 0(48) à 9(57). Cela signifie qu'il doit devenir -7 ( '0' - 55).
Maintenant, nous pourrions simplement multiplier par 7. Mais puisque -1 est représenté par tous les bits étant 1, nous pouvons utiliser à la place & -7depuis (0 & -7) == 0et (-1 & -7) == -7.
Quelques considérations supplémentaires:
Je n'ai pas utilisé de deuxième variable de boucle pour l'indexation c, car la mesure montre que son calcul iest moins cher.
Utiliser exactement i < bytes.Lengthla limite supérieure de la boucle permet au JITter d'éliminer les vérifications de limites bytes[i], j'ai donc choisi cette variante.
Faire bun int permet des conversions inutiles de et vers octet.
+1 pour avoir correctement cité votre source après avoir invoqué ce morceau de magie noire. Je salue tous Cthulhu.
Edward
4
Qu'en est-il de la chaîne à octet []?
Syaiful Nizam Yahya
9
Agréable! Pour ceux qui ont besoin d'une sortie en minuscules, l'expression change évidemment en87 + b + (((b-10)>>31)&-39)
eXavier
2
@AaA Vous avez dit " byte[] array", ce qui signifie littéralement un tableau de tableaux d'octets, ou byte[][]. Je me moquais juste.
CoolOppo
97
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.)
Encore plus court: String.Concat (Array.ConvertAll (octets, x => x.ToString ("X2"))
Nestor
14
Encore plus court: String.Concat (bytes.Select (b => b.ToString ("X2"))) [.NET4]
Allon Guralnek
14
Ne répond qu'à la moitié de la question.
Sly Gryphon
1
Pourquoi le second a-t-il besoin de .Net 4? String.Concat est dans .Net 2.0.
Polyfun
2
ces boucles de «style des années 90» sont généralement plus rapides, mais d'une quantité suffisamment négligeable pour que cela n'ait pas d'importance dans la plupart des contextes. Encore à mentionner
Austin_Anderson
69
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.
privatestaticreadonlyuint[] _lookup32 =CreateLookup32();privatestaticuint[]CreateLookup32(){var result =newuint[256];for(int i =0; i <256; i++){string s=i.ToString("X2");
result[i]=((uint)s[0])+((uint)s[1]<<16);}return result;}privatestaticstringByteArrayToHexViaLookup32(byte[] bytes){var lookup32 = _lookup32;var result =newchar[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);}returnnewstring(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 unsafefrère:
privatestaticreadonlyuint[] _lookup32Unsafe =CreateLookup32Unsafe();privatestaticreadonlyuint* _lookup32UnsafeP =(uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();privatestaticuint[]CreateLookup32Unsafe(){var result =newuint[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;}publicstaticstringByteArrayToHexViaLookup32Unsafe(byte[] bytes){var lookupP = _lookup32UnsafeP;var result =newchar[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]];}}returnnewstring(result);}
Ou si vous jugez acceptable d'écrire directement dans la chaîne:
publicstaticstringByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes){var lookupP = _lookup32UnsafeP;var result =newstring((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;}
Pourquoi la création de la table de recherche dans la version non sécurisée permute-t-elle les quartets de l'octet précalculé? Je pensais que l'endianité ne changeait que l'ordre des entités formées de plusieurs octets.
Raif Atef
@RaifAtef Ce qui importe ici n'est pas l'ordre des grignotages. Mais l'ordre des mots de 16 bits dans un entier de 32 bits. Mais j'envisage de le réécrire pour que le même code puisse s'exécuter indépendamment de l'endianité.
CodesInChaos
En relisant le code, je pense que vous l'avez fait parce que lorsque vous convertissez le caractère * plus tard en uint * et l'assignez (lors de la génération du caractère hexadécimal), le runtime / CPU inversera les octets (puisque uint n'est pas traité par le comme 2 caractères 16 bits distincts), vous devez donc les pré-retourner pour compenser. Ai-je raison ? L'endianisme est déroutant :-).
Raif Atef
4
Cela répond juste à la moitié de la question ... Que diriez-vous de la chaîne hexadécimale en octets?
Narvalex
3
@CodesInChaos Je me demande si Spanpeut être utilisé maintenant au lieu de unsafe??
Konrad
64
Vous pouvez utiliser la méthode BitConverter.ToString:
Je viens de rencontrer le même problème aujourd'hui, et je suis tombé sur ce code:
privatestaticstringByteArrayToHex(byte[] barray){char[] c =newchar[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);}returnnewstring(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).
sans compter que cela utilise le moins de mémoire. Aucune chaîne intermédiaire créée que ce soit.
Chochos
8
Ne répond qu'à la moitié de la question.
Sly Gryphon
C'est génial car cela fonctionne sur pratiquement toutes les versions de NET, y compris NETMF. Un gagnant!
Jonesome Reinstate Monica
1
La réponse acceptée fournit 2 excellentes méthodes HexToByteArray, qui représentent l'autre moitié de la question. La solution de Waleed répond à la question de savoir comment procéder sans créer un grand nombre de chaînes dans le processus.
Brendten Eickstaedt
Est-ce que la nouvelle chaîne (c) copie et ré-alloue ou est-elle suffisamment intelligente pour savoir quand elle peut simplement encapsuler le caractère []?
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.ToByteet String.Substringsi 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.ToBytesi vous avez besoin de performances. Ne pas utiliser autre chose que , String.Substringen 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:
publicstaticbyte[]HexadecimalStringToByteArray_Original(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];for(var i =0; i < outputLength; i++)
output[i]=Convert.ToByte(input.Substring(i *2,2),16);return output;}
Révision 4:
publicstaticbyte[]HexadecimalStringToByteArray_Rev4(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];
using (var sr =newStringReader(input)){for(var i =0; i < outputLength; i++)
output[i]=Convert.ToByte(newstring(newchar[2]{(char)sr.Read(),(char)sr.Read()}),16);}return output;}
La révision évite String.Substringet utilise un à la StringReaderplace. 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.ToBytequand 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.
publicstaticbyte[]HexadecimalStringToByteArray(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];var numeral =newchar[2];
using (var sr =newStringReader(input)){for(var i =0; i < outputLength; i++){
numeral[0]=(char)sr.Read();
numeral[1]=(char)sr.Read();
output[i]=Convert.ToByte(newstring(numeral),16);}}return output;}
Chaque hexadécimal numeralreprésente un seul octet utilisant deux chiffres (symboles).
Mais alors, pourquoi appeler StringReader.Readdeux 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.
publicstaticbyte[]HexadecimalStringToByteArray(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];var numeral =newchar[2];
using (var sr =newStringReader(input)){for(var i =0; i < outputLength; i++){var read = sr.Read(numeral,0,2);Debug.Assert(read ==2);
output[i]=Convert.ToByte(newstring(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 jpar 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.CopyTosur 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- CopyTovous; c'est plus simple, plus clair et plus efficace.
Avez-vous vraiment besoin d'un jindex qui s'incrémente par incréments de deux parallèlement à i? Bien sûr que non, il suffit de multiplier ipar deux (que le compilateur devrait être en mesure d'optimiser pour un ajout).
publicstaticbyte[]HexadecimalStringToByteArray_BestEffort(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];var numeral =newchar[2];for(int i =0; i < outputLength; i++){
input.CopyTo(i *2, numeral,0,2);
output[i]=Convert.ToByte(newstring(numeral),16);}return output;}
À quoi ressemble la solution maintenant? Exactement comme au début, mais au lieu d'utiliser String.Substringpour 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.Substringpourra également l'éviter dans ces cas.
En fait, si vous regardez à String.Substringnouveau, 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é CopyTodirectement par là pour éviter les frais généraux d'appel.
String.Substring
Pire: une allocation rapide, une copie rapide.
Meilleur cas: pas d'allocation, pas de copie.
Méthode manuelle
Pire: deux allocations normales, une copie normale, une copie rapide.
Meilleur cas: une allocation normale, une copie normale.
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.ToByteet String.Substringest 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.60GHzCores:8CurrentClockSpeed:2600MaxClockSpeed:2600--------------------Parsing hexadecimal stringinto an array of bytes
--------------------HexadecimalStringToByteArray_Original:7,777.09 average ticks (over 10000 runs),1.2XHexadecimalStringToByteArray_BestEffort:8,550.82 average ticks (over 10000 runs),1.1XHexadecimalStringToByteArray_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.
erreur: {"Impossible de trouver des chiffres reconnaissables."}
Priya Jagtap
17
Complément pour répondre par @CodesInChaos (méthode inversée)
publicstaticbyte[]HexToByteUsingByteManipulation(string s){byte[] bytes =newbyte[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:
Table du codeur 512 octets ou 1024 octets (deux fois la taille si les majuscules et les minuscules sont nécessaires)
Table du décodeur 256 octets ou 64 Ko (soit une recherche à un seul caractère soit une recherche à deux caractères)
Ma solution utilise 1024 octets pour la table de codage et 256 octets pour le décodage.
Décodage
privatestaticreadonlybyte[]LookupTable=newbyte[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,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};privatestaticbyteLookup(char c){var b =LookupTable[c];if(b ==255)thrownewIOException("Expected a hex character, got "+ c);return b;}publicstaticbyteToByte(char[] chars,int offset){return(byte)(Lookup(chars[offset])<<4|Lookup(chars[offset +1]));}
Codage
privatestaticreadonlychar[][]LookupTableUpper;privatestaticreadonlychar[][]LookupTableLower;staticHex(){LookupTableLower=newchar[256][];LookupTableUpper=newchar[256][];for(var i =0; i <256; i++){LookupTableLower[i]= i.ToString("x2").ToCharArray();LookupTableUpper[i]= i.ToString("X2").ToCharArray();}}publicstaticchar[]ToCharLower(byte[] b,int bOffset){returnLookupTableLower[b[bOffset]];}publicstaticchar[]ToCharUpper(byte[] b,int bOffset){returnLookupTableUpper[b[bOffset]];}
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.
L'utilisation de la mémoire de 256 octets est négligeable lorsque vous exécutez du code sur le CLR.
dolmen
9
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.
privatebyte[]HexStringToByteArray(string hexString){int hexStringLength = hexString.Length;byte[] b =newbyte[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;}
Ce code suppose que la chaîne hexadécimale utilise des caractères alpha majuscules et explose si la chaîne hexadécimale utilise l'alpha minuscule. Pourrait être sûr de vouloir faire une conversion "majuscule" sur la chaîne d'entrée.
Marc Novakowski
Voilà une observation astucieuse Marc. Le code a été écrit pour inverser la solution de Waleed. L'appel ToUpper ralentirait l'algorithme, mais lui permettrait de gérer les caractères alpha en minuscules.
Chris F
3
Convert.ToByte (topChar + bottomChar) peut être écrit comme (octet) (topChar + bottomChar)
Amir Rezaei
Pour gérer les deux cas sans pénalité de performance importante,hexString[i] &= ~0x20;
Ben Voigt
9
Pourquoi le rendre complexe? C'est simple dans Visual Studio 2008:
la raison en est la performance, lorsque vous avez besoin d'une solution haute performance. :)
Ricky
7
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:04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939fTime to parse 100,000 times:50.4192 ms
Resultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9FAccepted answer:(StringToByteArray)Time to parse 100000 times:233.1264msResultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9FWithMono's implementation:Time to parse 100000 times:777.2544msResultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9FWithSoapHexBinary:Time to parse 100000 times:845.1456msResultas 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-57-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:
publicstaticbyte[]ToByteArrayFromHex(string hexString){if(hexString.Length%2!=0)thrownewArgumentException("String must have an even length");vararray=newbyte[hexString.Length/2];for(int i =0; i < hexString.Length; i +=2){array[i/2]=ByteFromTwoChars(hexString[i], hexString[i +1]);}returnarray;}privatestaticbyteByteFromTwoChars(char p,char p_2){byte ret;if(p <='9'&& p >='0'){
ret =(byte)((p -'0')<<4);}elseif(p <='f'&& p >='a'){
ret =(byte)((p -'a'+10)<<4);}elseif(p <='F'&& p >='A'){
ret =(byte)((p -'A'+10)<<4);}elsethrownewArgumentException("Char is not a hex digit: "+ p,"p");if(p_2 <='9'&& p_2 >='0'){
ret |=(byte)((p_2 -'0'));}elseif(p_2 <='f'&& p_2 >='a'){
ret |=(byte)((p_2 -'a'+10));}elseif(p_2 <='F'&& p_2 >='A'){
ret |=(byte)((p_2 -'A'+10));}elsethrownewArgumentException("Char is not a hex digit: "+ p_2,"p_2");return ret;}
J'ai essayé quelques trucs avec unsafeet déplacé la ifsé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.)
Pour les adeptes de Knuth: je l'ai fait parce que j'ai besoin d'analyser quelques milliers de chaînes hexadécimales toutes les quelques minutes environ, il est donc important que ce soit aussi rapide que possible (dans la boucle interne, pour ainsi dire). La solution de Tomalak n'est pas notablement plus lente si de nombreuses analyses de ce type ne se produisent pas.
Ben Mosher
5
Versions sûres:
publicstaticclassHexHelper{[System.Diagnostics.Contracts.Pure]publicstaticstringToHex(thisbyte[]value){if(value==null)thrownewArgumentNullException("value");conststring hexAlphabet =@"0123456789ABCDEF";var chars =newchar[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];}}returnnewstring(chars);}[System.Diagnostics.Contracts.Pure]publicstaticbyte[]FromHex(thisstringvalue){if(value==null)thrownewArgumentNullException("value");if(value.Length%2!=0)thrownewArgumentException("Hexadecimal value length must be even.","value");unchecked{byte[] result =newbyte[value.Length/2];for(int i =0; i < result.Length; i++){// 0(48) - 9(57) -> 0 - 9// A(65) - F(70) -> 10 - 15int 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.
publicstaticclassHexUnsafeHelper{[System.Diagnostics.Contracts.Pure]publicstaticunsafestringToHex(thisbyte[]value){if(value==null)thrownewArgumentNullException("value");conststring alphabet =@"0123456789ABCDEF";string result =newstring(' ',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]publicstaticunsafebyte[]FromHex(thisstringvalue){if(value==null)thrownewArgumentNullException("value");if(value.Length%2!=0)thrownewArgumentException("Hexadecimal value length must be even.","value");unchecked{byte[] result =newbyte[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 - 15int 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):
publicstaticbyte[]HexToBytes(thisstring hexString){byte[] b =newbyte[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:
publicstaticstringBytesToHex(thisbyte[] barray,bool toLowerCase =true){byte addByte =0x37;if(toLowerCase) addByte =0x57;char[] c =newchar[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);}returnnewstring(c);}
Méthodes d'extension (avertissement: code complètement non testé, BTW ...):
publicstaticclassByteExtensions{publicstaticstringToHexString(thisbyte[] ba){StringBuilder hex =newStringBuilder(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).
Vous devriez probablement tester le code avant de le proposer pour une question comme celle-ci.
2017
3
Des développeurs de Microsoft, une conversion simple et agréable:
publicstaticstringByteArrayToString(byte[] ba){// Concatenate the bytes into one long stringreturn ba.Aggregate(newStringBuilder(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 :
publicstaticstringByteArrayToString(byte[] ba){StringBuilder hex =newStringBuilder(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.
si Source == nullou Source.Length == 0nous avons un problème monsieur!
Andrei Krasutski
2
En termes de vitesse, cela semble être mieux que tout ici:
publicstaticstringToHexString(byte[] data){byte b;int i, j, k;int l = data.Length;char[] r =newchar[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);}returnnewstring(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.
publicstaticbyte[]StringToByteArray2(string hex){byte[] bytes =newbyte[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;}
staticprivatereadonlychar[] hexAlphabet =newchar[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};staticstringByteArrayToHexViaByteManipulation3(byte[] bytes){char[] c =newchar[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];}returnnewstring(c);}
Et je pense que celui-ci est une optimisation:
staticprivatereadonlychar[] hexAlphabet =newchar[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};staticstringByteArrayToHexViaByteManipulation4(byte[] bytes){char[] c =newchar[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];}returnnewstring(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 StringBuilderméthodes d' appel prendront également du temps.
publicstaticStringToHex(byte[] data){int dataLength = data.Length;// pre-create the stringbuilder using the length of the data * 2, precisely enoughStringBuilder sb =newStringBuilder(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 letterint 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 characterint 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();}publicstaticbyte[]FromHex(String hex){// pre-create the arrayint resultLength = hex.Length/2;byte[] result =newbyte[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 basedvalue=((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){thrownewArgumentException("Hexadecimal encoding incorrect for input "+ hex);}return result;}
Hmm, je devrais vraiment l'optimiser Char[]et l'utiliser en Charinterne au lieu des pouces ...
Maarten Bodewes
Pour C #, l'initialisation des variables où elles sont utilisées, au lieu de sortir de la boucle, est probablement préférable pour permettre au compilateur d'optimiser. J'obtiens des performances équivalentes de toute façon.
Peteter
2
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 offsetau lieu de offsetet offset + 1pourrait donner des avantages théoriques, mais je soupçonne que le compilateur gère cela mieux que moi.
privatestaticreadonlybyte[]LookupTableLow=newbyte[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,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};privatestaticreadonlybyte[]LookupTableHigh=newbyte[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,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};privatestaticbyteLookupLow(char c){var b =LookupTableLow[c];if(b ==255)thrownewIOException("Expected a hex character, got "+ c);return b;}privatestaticbyteLookupHigh(char c){var b =LookupTableHigh[c];if(b ==255)thrownewIOException("Expected a hex character, got "+ c);return b;}publicstaticbyteToByte(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é.
publicstaticbyte[]FromHexString(string src){if(String.IsNullOrEmpty(src))returnnull;int index = src.Length;int sz = index /2;if(sz <=0)returnnull;byte[] rc =newbyte[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;}
Deux mashups qui plient les deux opérations de grignotage en une seule.
Version probablement assez efficace:
publicstaticstringByteArrayToString2(byte[] ba){char[] c =newchar[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));}returnnewstring( c );}
Version décadente linq-with-bit-hacking:
publicstaticstringByteArrayToString(byte[] ba){returnstring.Concat( ba.SelectMany( b =>newint[]{ b >>4, b &0xF}).Select( b =>(char)(55+ b +(((b-10)>>31)&-7))));}
Et inverser:
publicstaticbyte[]HexStringToByteArray(string s ){byte[] ab =newbyte[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;}
HexStringToByteArray ("09") renvoie 0x02, ce qui est mauvais
CoperNick
1
Une autre façon consiste stackallocà réduire la pression de la mémoire GC:
staticstringByteToHexBitFiddle(byte[] bytes){var c =stackallocchar[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';returnnewstring(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 foreachpour 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:
Les pénalités pour l'indexation dans une chaîne pour effectuer la recherche par rapport à un tableau de caractères sont importantes dans le test de fichiers volumineux.
Les pénalités pour l'utilisation d'un StringBuilder de capacité connue par rapport à un tableau de caractères de taille connue pour créer la chaîne sont encore plus importantes.
Voici le code:
using System;
namespace ConversionExtensions{publicstaticclassByteArrayExtensions{privatereadonlystaticchar[] digits =newchar[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};publicstaticstringToHexString(thisbyte[] bytes){char[] hex =newchar[bytes.Length*2];int index =0;foreach(byte b in bytes){
hex[index++]= digits[b >>4];
hex[index++]= digits[b &0x0F];}returnnewstring(hex);}}}
using System;
using System.IO;
namespace ConversionExtensions{publicstaticclassStringExtensions{publicstaticbyte[]ToBytes(thisstring hexString){if(!string.IsNullOrEmpty(hexString)&& hexString.Length%2!=0){thrownewFormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");}
hexString = hexString.ToUpperInvariant();byte[] data =newbyte[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){thrownewFormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");}else{bytevalue=(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.
Réponses:
Soit:
ou:
Il y a encore plus de variantes de le faire, par exemple ici .
La conversion inverse se passerait comme ceci:
L'utilisation
Substring
est la meilleure option en combinaison avecConvert.ToByte
. Voir cette réponse pour plus d'informations. Si vous avez besoin de meilleures performances, vous devez éviterConvert.ToByte
avant de pouvoir abandonnerSubString
.la source
Analyse de performance
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
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.
Résultats
unsafe
(via CodesInChaos) (ajouté au test repo par airbreather )BitConverter
(via Tomalak){SoapHexBinary}.ToString
(via Mykroft){byte}.ToString("X2")
(en utilisantforeach
) (dérivé de la réponse de Will Dean){byte}.ToString("X2")
(utilisation{IEnumerable}.Aggregate
, nécessite System.Linq) (via Mark)Array.ConvertAll
(en utilisantstring.Join
) (via Will Dean)Array.ConvertAll
(en utilisantstring.Concat
, nécessite .NET 4.0) (via Will Dean){StringBuilder}.AppendFormat
(en utilisantforeach
) (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.
Code de test
N'hésitez pas à jouer avec le code de test que j'ai utilisé. Une version est incluse ici, mais n'hésitez pas à cloner le 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.Mise à jour (2010-01-13)
Ajout de la réponse de Waleed à l'analyse. Tres rapide.
Mise à jour (2011-10-05)
Ajout d'une
string.Concat
Array.ConvertAll
variante pour être complet (nécessite .NET 4.0). Au même niveau que lastring.Join
version.Mise à jour (2012-02-05)
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, maisBitConverter
gagne toujours.Mise à jour (2012-04-03)
Ajout de la
SoapHexBinary
réponse de Mykroft à l'analyse, qui a pris la troisième place.Mise à jour (2013-01-15)
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).
Mise à jour (2013-05-23)
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).
Mise à jour (2014-07-31)
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.
Mise à jour (2015-08-20)
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.la source
bytes.ToHexStringAtLudicrousSpeed()
).Il existe une classe appelée SoapHexBinary qui fait exactement ce que vous voulez.
la source
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.
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn
Une explication du bricolage bizarre:
bytes[i] >> 4
extrait le quartet haut d'un octetbytes[i] & 0xF
extrait le quartet bas d'un octetb - 10
est
< 0
pour les valeursb < 10
, qui deviendra un chiffre décimalest
>= 0
pour les valeursb > 10
, qui deviendra une lettre deA
àF
.i >> 31
sur un entier signé de 32 bits extrait le signe, grâce à l'extension de signe. Ce sera-1
pouri < 0
et0
pouri >= 0
.(b-10)>>31
sera0
pour les lettres et-1
pour les chiffres.0
et seb
situe dans la plage de 10 à 15. Nous voulons la mapper surA
(65) àF
(70), ce qui implique d'ajouter 55 ('A'-10
).b
de la plage 0 à 9 à la plage0
(48) à9
(57). Cela signifie qu'il doit devenir -7 ('0' - 55
).Maintenant, nous pourrions simplement multiplier par 7. Mais puisque -1 est représenté par tous les bits étant 1, nous pouvons utiliser à la place
& -7
depuis(0 & -7) == 0
et(-1 & -7) == -7
.Quelques considérations supplémentaires:
c
, car la mesure montre que son calculi
est moins cher.i < bytes.Length
la limite supérieure de la boucle permet au JITter d'éliminer les vérifications de limitesbytes[i]
, j'ai donc choisi cette variante.b
un int permet des conversions inutiles de et vers octet.la source
hex string
àbyte[] array
?87 + b + (((b-10)>>31)&-39)
byte[] array
", ce qui signifie littéralement un tableau de tableaux d'octets, oubyte[][]
. 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:Ou, si vous utilisez .NET 4.0:
(Ce dernier à partir d'un commentaire sur le message d'origine.)
la source
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.
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:Ou si vous jugez acceptable d'écrire directement dans la chaîne:
la source
Span
peut être utilisé maintenant au lieu deunsafe
??Vous pouvez utiliser la méthode BitConverter.ToString:
Production:
Pour plus d'informations: Méthode BitConverter.ToString (octet [])
la source
Je viens de rencontrer le même problème aujourd'hui, et je suis tombé sur ce code:
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).
la source
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
etString.Substring
si vous êtes pressé ("Code original" ci-dessous), c'est la meilleure combinaison si vous ne voulez pas réimplémenterConvert.ToByte
. Utilisez quelque chose de plus avancé (voir les autres réponses) qui ne sert pasConvert.ToByte
si vous avez besoin de performances. Ne pas utiliser autre chose que ,String.Substring
en combinaison avecConvert.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:
Révision 4:
La révision évite
String.Substring
et utilise un à laStringReader
place. La raison donnée est:Eh bien, en regardant le code de référence pour
String.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.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.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 (commej
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'appelerString.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.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 multiplieri
par deux (que le compilateur devrait être en mesure d'optimiser pour un ajout).À 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, maisString.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 utiliser
Convert.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 battreString.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
etString.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ètementString
).Je soupçonne que les personnes qui signalent de meilleures performances en «évitant
String.Substring
» évitent égalementConvert.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?
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.
S'amuser! (Mais optimisez avec modération.)
la source
Complément pour répondre par @CodesInChaos (méthode inversée)
Explication:
& 0x0f
est de prendre en charge également les lettres minusculeshi = 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'esthi = ch - 48
(c'est à cause de0xffffffff & 7
).Pour 'A' .. 'F' c'est
hi = ch - 65 + 10;
(c'est à cause de0x00000000 & 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...
).la source
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.
Décodage
Codage
Comparaison
* cette solution
Remarque
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.
la source
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.
la source
hexString[i] &= ~0x20;
Pourquoi le rendre complexe? C'est simple dans Visual Studio 2008:
C #:
VB:
la source
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):
Les lignes base64 et «BitConverter'd» sont là pour tester l'exactitude. Notez qu'ils sont égaux.
La mise en oeuvre:
J'ai essayé quelques trucs avec
unsafe
et déplacé laif
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.)
la source
Versions sûres:
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.
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).
la source
Fonction inverse pour le code Waleed Eissa (Hex String To Byte Array):
Fonction Eissa Waleed avec prise en charge des minuscules:
la source
Méthodes d'extension (avertissement: code complètement non testé, BTW ...):
etc. Utilisez l'une des trois solutions de Tomalak (la dernière étant une méthode d'extension sur une chaîne).
la source
Des développeurs de Microsoft, une conversion simple et agréable:
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 :
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.
la source
b.ToSting("X2")
.Et pour l'insertion dans une chaîne SQL (si vous n'utilisez pas de paramètres de commande):
la source
Source == null
ouSource.Length == 0
nous avons un problème monsieur!En termes de vitesse, cela semble être mieux que tout ici:
la source
Je n'ai pas obtenu le code que vous avez suggéré de travailler, Olipro.
hex[i] + hex[i+1]
apparemment retourné unint
.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.
la source
Cette version de ByteArrayToHexViaByteManipulation pourrait être plus rapide.
De mes rapports:
...
Et je pense que celui-ci est une optimisation:
la source
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.Converti à partir du code Java.
la source
Char[]
et l'utiliser enChar
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++
etoffset
au lieu deoffset
etoffset + 1
pourrait donner des avantages théoriques, mais je soupçonne que le compilateur gère cela mieux que moi.C'est juste au dessus de ma tête et n'a pas été testé ou comparé.
la source
Encore une autre variation pour la diversité:
la source
Pas optimisé pour la vitesse, mais plus LINQy que la plupart des réponses (.NET 4.0):
la source
Deux mashups qui plient les deux opérations de grignotage en une seule.
Version probablement assez efficace:
Version décadente linq-with-bit-hacking:
Et inverser:
la source
Une autre façon consiste
stackalloc
à réduire la pression de la mémoire GC:la source
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:
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.
la source
Une autre fonction rapide ...
la source