La respuesta aceptada a continuación parece asignar una horrible cantidad de cadenas en la conversión de cadenas a bytes. Me pregunto cómo esto afecta el rendimiento
Wim Coenen
99
La clase SoapHexBinary hace exactamente lo que quieres, creo.
Mykroft
Me parece que hacer 2 preguntas en 1 publicación no es del todo estándar.
SandRock
Respuestas:
1353
Ya sea:
publicstaticstringByteArrayToString(byte[] ba){StringBuilder hex =newStringBuilder(ba.Length*2);foreach(byte b in ba)
hex.AppendFormat("{0:x2}", b);return hex.ToString();}
Hay incluso más variantes de hacerlo, por ejemplo aquí .
La conversión inversa sería así:
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;}
Usar Substringes la mejor opción en combinación con Convert.ToByte. Vea esta respuesta para más información. Si necesita un mejor rendimiento, debe evitarlo Convert.ToByteantes de poder caer SubString.
Estás usando SubString. ¿Este bucle no asigna una cantidad horrible de objetos de cadena?
Wim Coenen
30
Honestamente, hasta que se reduzca drásticamente el rendimiento, tendería a ignorar esto y confiar en el Runtime y el GC para encargarse de ello.
Tomalak
87
Debido a que un byte es dos nibbles, cualquier cadena hexadecimal que represente válidamente una matriz de bytes debe tener un recuento de caracteres par. No se debe agregar un 0 en ninguna parte: agregar uno supondría que los datos no válidos son potencialmente peligrosos. En todo caso, el método StringToByteArray debería lanzar una FormatException si la cadena hexadecimal contiene un número impar de caracteres.
David Boike
77
@ 00jt Debe suponer que F == 0F. O es lo mismo que 0F, o la entrada se recortó y F es en realidad el comienzo de algo que no ha recibido. Depende de su contexto hacer esas suposiciones, pero creo que una función de propósito general debería rechazar los caracteres impares como inválidos en lugar de hacer esa suposición para el código de llamada.
David Boike
11
@DavidBoike La pregunta no tenía NADA que ver con "cómo manejar valores de flujo posiblemente recortados" Se trata de una cadena. String myValue = 10.ToString ("X"); myValue es "A", no "0A". Ahora ve a leer esa cadena de nuevo a bytes, oops, la rompiste.
00jt
488
Análisis de rendimiento
Nota: nuevo líder a partir del 2015-08-20.
Ejecuté cada uno de los diversos métodos de conversión a través de algunas Stopwatchpruebas de rendimiento en bruto , una ejecución con una oración aleatoria (n = 61, 1000 iteraciones) y una ejecución con un texto del Proyecto Gutenburg (n = 1,238,957, 150 iteraciones). Aquí están los resultados, aproximadamente del más rápido al más lento. Todas las mediciones están en ticks ( 10,000 ticks = 1 ms ) y todas las notas relativas se comparan con la StringBuilderimplementación [más lenta] . Para el código utilizado, vea a continuación o el repositorio de marco de prueba donde ahora mantengo el código para ejecutar esto.
Descargo de responsabilidad
ADVERTENCIA: No confíe en estas estadísticas para nada concreto; son simplemente una muestra de datos de muestra. Si realmente necesita un rendimiento de primer nivel, pruebe estos métodos en un entorno representativo de sus necesidades de producción con datos representativos de lo que utilizará.
Las tablas de búsqueda han tomado la delantera sobre la manipulación de bytes. Básicamente, hay alguna forma de precomputar lo que cualquier mordisco o byte dado estará en hexadecimal. Luego, mientras revisa los datos, simplemente busca la siguiente porción para ver qué cadena hexadecimal sería. Ese valor se agrega a la salida de cadena resultante de alguna manera. Durante mucho tiempo, la manipulación de bytes, potencialmente más difícil de leer por algunos desarrolladores, fue el enfoque de mayor rendimiento.
Su mejor opción será encontrar algunos datos representativos y probarlos en un entorno similar a la producción. Si tiene diferentes restricciones de memoria, puede preferir un método con menos asignaciones a uno que sea más rápido pero consuma más memoria.
Código de prueba
Siéntase libre de jugar con el código de prueba que utilicé. Aquí se incluye una versión, pero puede clonar el repositorio y agregar sus propios métodos. Envíe una solicitud de extracción si encuentra algo interesante o desea ayudar a mejorar el marco de prueba que utiliza.
Agregue el nuevo método estático ( Func<byte[], string>) a /Tests/ConvertByteArrayToHexString/Test.cs.
Agregue el nombre de ese método al TestCandidatesvalor de retorno en esa misma clase.
Asegúrese de estar ejecutando la versión de entrada que desea, oración o texto, alternando los comentarios GenerateTestInputen esa misma clase.
Presione F5y espere la salida (también se genera un volcado de HTML en la carpeta / 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();}
Actualización (2010-01-13)
Se agregó la respuesta de Waleed al análisis. Bastante rapido.
Actualización (2011-10-05)
string.ConcatArray.ConvertAllVariante agregada para completar (requiere .NET 4.0). A la par destring.Join versión.
Actualización (2012-02-05)
El repositorio de prueba incluye más variantes como StringBuilder.Append(b.ToString("X2")). Ninguno alteró los resultados. foreaches más rápido que {IEnumerable}.Aggregate, por ejemplo, pero BitConverteraún así gana.
Actualización (2012-04-03)
Se agregó la SoapHexBinaryrespuesta de Mykroft al análisis, que tomó el tercer lugar.
Actualización (2013-01-15)
Se agregó la respuesta de manipulación de bytes de CodesInChaos, que ocupó el primer lugar (por un gran margen en grandes bloques de texto).
Actualización (2013-05-23)
Se agregó la respuesta de búsqueda de Nathan Moinvaziri y la variante del blog de Brian Lambert. Ambos bastante rápido, pero sin tomar la iniciativa en la máquina de prueba que utilicé (AMD Phenom 9750).
Actualización (2014-07-31)
Se agregó la nueva respuesta de búsqueda basada en bytes de @ CodesInChaos. Parece haber tomado la delantera tanto en las pruebas de oraciones como en las pruebas de texto completo.
Actualización (2015-08-20)
Se agregaron optimizaciones y unsafevariantes de airbreather al repositorio de esta respuesta . Si quieres jugar en el juego inseguro, puedes obtener grandes ganancias de rendimiento sobre cualquiera de los ganadores principales anteriores tanto en cadenas cortas como en textos grandes.
A pesar de hacer que el código esté disponible para que usted haga lo que solicitó por su cuenta, actualicé el código de prueba para incluir la respuesta de Waleed. Dejando a un lado todo el mal humor, es mucho más rápido.
patridge
2
@CodesInChaos Hecho. Y también ganó bastante en mis pruebas. Todavía no pretendo comprender completamente ninguno de los métodos principales, pero se ocultan fácilmente de la interacción directa.
patridge
66
Esta respuesta no tiene intención de responder a la pregunta de qué es "natural" o común. El objetivo es dar a las personas algunos puntos de referencia básicos de rendimiento ya que, cuando necesita hacer estas conversiones, tiende a hacerlas mucho. Si alguien necesita velocidad bruta, simplemente ejecuta los puntos de referencia con algunos datos de prueba apropiados en su entorno informático deseado. Luego, guarde ese método en un método de extensión donde nunca vuelva a mirar su implementación (por ejemplo, bytes.ToHexStringAtLudicrousSpeed()).
patridge
2
Acabo de producir una implementación basada en una tabla de búsqueda de alto rendimiento. Su variante segura es aproximadamente un 30% más rápida que el líder actual en mi CPU. Las variantes inseguras son aún más rápidas. stackoverflow.com/a/24343727/445517
CodesInChaos
244
Hay una clase llamada SoapHexBinary que hace exactamente lo que quieres.
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 desde .NET 1.0 y está en mscorlib. A pesar de que es un espacio de nombres divertido, hace exactamente lo que hizo la pregunta.
Sly Gryphon
44
Gran descubrimiento! Tenga en cuenta que deberá rellenar cadenas impares con un 0 inicial para GetStringToBytes, como la otra solución.
Carter Medlin
¿Has visto el pensamiento de implementación? La respuesta aceptada tiene una mejor en mi humilde opinión.
SoapHexBinary no es compatible con .NET Core / .NET Standard ...
juFo
141
Al escribir código criptográfico, es común evitar ramas dependientes de datos y búsquedas de tablas para garantizar que el tiempo de ejecución no dependa de los datos, ya que el tiempo dependiente de datos puede conducir a ataques de canal lateral.
También es bastante rápido.
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] >> 4extrae el mordisco alto de un byte bytes[i] & 0xFextrae el mordisco bajo de un byte
b - 10
es < 0para los valores b < 10, que se convertirá en un dígito decimal
es >= 0para los valores b > 10, que se convertirán en una carta Aa F.
El uso i >> 31de un entero de 32 bits con signo extrae el signo, gracias a la extensión de signo. Será -1por i < 0y 0para i >= 0.
Combinando 2) y 3), muestra que (b-10)>>31será 0para letras y -1para dígitos.
Mirando las mayúsculas y minúsculas para las letras, el último sumando se convierte en 0, y bestá en el rango de 10 a 15. Queremos asignarlo a A(65) a F(70), lo que implica sumar 55 ( 'A'-10).
Mirando el caso de los dígitos, queremos adaptar el último sumando para que se asigne bdel rango de 0 a 9 al rango 0(48) a 9(57). Esto significa que necesita convertirse en -7 ( '0' - 55).
Ahora podríamos simplemente multiplicar por 7. Pero dado que -1 está representado por todos los bits que son 1, podemos usar & -7desde entonces (0 & -7) == 0y (-1 & -7) == -7.
Algunas consideraciones adicionales:
No utilicé una segunda variable de bucle para indexar c, ya que la medición muestra que calcularlo desdei es más barato.
Usar exactamente i < bytes.Lengthcomo límite superior del bucle permite que el JITter elimine las comprobaciones de límites bytes[i], por lo que elegí esa variante.
Hacer bun int permite conversiones innecesarias desde y hacia byte.
Aún más corto: String.Concat (Array.ConvertAll (bytes, x => x.ToString ("X2"))
Nestor el
14
Aún más corto: String.Concat (bytes.Select (b => b.ToString ("X2"))) [.NET4]
Allon Guralnek
14
Solo responde la mitad de la pregunta.
Sly Gryphon
1
¿Por qué el segundo necesita .Net 4? String.Concat está en .Net 2.0.
Polyfun
2
esos bucles de "estilo de los 90" son generalmente más rápidos, pero en una cantidad lo suficientemente insignificante que no importará en la mayoría de los contextos. Todavía vale la pena mencionarlo
Austin_Anderson
69
Otro enfoque basado en la tabla de búsqueda. Éste usa solo una tabla de búsqueda para cada byte, en lugar de una tabla de búsqueda por mordisco.
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);}
También he probado variantes de este usando ushort, struct{char X1, X2}, struct{byte X1, X2}en la tabla de búsqueda.
Dependiendo del objetivo de compilación (x86, X64), esos tenían aproximadamente el mismo rendimiento o eran ligeramente más lentos que esta variante.
Y para un rendimiento aún mayor, su unsafehermano:
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);}
O si considera aceptable escribir directamente en la cadena:
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;}
¿Por qué la creación de la tabla de búsqueda en la versión insegura intercambia los nibbles del byte precalculado? Pensé que la endianidad solo cambió el orden de las entidades que se formaron de múltiples bytes.
Raif Atef
@RaifAtef Lo que importa aquí no es el orden de los mordiscos. Pero el orden de las palabras de 16 bits en un entero de 32 bits. Pero estoy considerando reescribirlo para que se pueda ejecutar el mismo código independientemente de la endianidad.
CodesInChaos
Al releer el código, creo que hiciste esto porque cuando lanzas el char * más tarde a un uint * y lo asignas (al generar el char hexadecimal), el tiempo de ejecución / CPU volteará los bytes (ya que uint no se trata igual que 2 caracteres separados de 16 bits), por lo que los voltea previamente para compensar. Estoy en lo cierto? Endianness es confuso :-).
Raif Atef
44
Esto solo responde a la mitad de la pregunta ... ¿Qué tal una cadena hexadecimal a bytes?
Narvalex
3
@CodesInChaos Me pregunto si Spanse puede usar ahora en lugar de unsafe??
Acabo de encontrar el mismo problema hoy, y me encontré con este código:
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);}
Fuente: Byte de publicación del foro [] Array to Hex String (ver la publicación de PZahra). Modifiqué un poco el código para eliminar el prefijo 0x.
Hice algunas pruebas de rendimiento del código y fue casi ocho veces más rápido que usar BitConverter.ToString () (el más rápido según la publicación de Patridge).
sin mencionar que esto usa menos memoria. No hay cadenas intermedias creadas en absoluto.
Chochos
8
Solo responde la mitad de la pregunta.
Sly Gryphon
Esto es genial porque funciona básicamente en cualquier versión de NET, incluido NETMF. ¡Un ganador!
Jonesome Reinstate a Monica
1
La respuesta aceptada proporciona 2 excelentes métodos HexToByteArray, que representan la otra mitad de la pregunta. La solución de Waleed responde a la pregunta de cómo hacer esto sin crear una gran cantidad de cadenas en el proceso.
Brendten Eickstaedt
¿La nueva cadena (c) copia y reasigna o es lo suficientemente inteligente como para saber cuándo puede simplemente envolver el char []?
Explicaré que esta edición es incorrecta y explicaré por qué podría revertirse. En el camino, es posible que aprenda una o dos cosas sobre algunas partes internas y vea otro ejemplo más de lo que realmente es la optimización prematura y cómo puede morderlo.
tl; dr: simplemente use Convert.ToBytey String.Substringsi tiene prisa ("Código original" a continuación), es la mejor combinación si no desea volver a implementar Convert.ToByte. Use algo más avanzado (vea otras respuestas) que no se usa Convert.ToBytesi necesita rendimiento. No , no utilizar ninguna otra cosa que no sea String.Substringen combinación conConvert.ToByte , a menos que alguien tiene algo interesante que decir acerca de esto en los comentarios de esta respuesta.
advertencia: esta respuesta puede volverse obsoleta si unConvert.ToByte(char[], Int32) se implementa sobrecarga en el marco. Es poco probable que esto suceda pronto.
Como regla general, no me gusta mucho decir "no optimices prematuramente", porque nadie sabe cuándo es "prematuro". Lo único que debe tener en cuenta al decidir si optimizar o no es: "¿Tengo el tiempo y los recursos para investigar los enfoques de optimización correctamente?". Si no lo hace, entonces es demasiado pronto, espere hasta que su proyecto es más maduro o hasta que necesite el rendimiento (si hay una necesidad real, entonces usted va a hacer que el tiempo). Mientras tanto, haga lo más simple que podría funcionar en su lugar.
Código original:
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;}
Revisión 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 revisión evita String.Substringy utiliza un StringReaderen su lugar. La razón dada es:
Editar: puede mejorar el rendimiento de cadenas largas utilizando un analizador de un solo paso, de esta manera:
Bueno, mirando el código de referenciaString.Substring , ya es claramente "de un solo paso"; y por qué no debería ser? Funciona a nivel de byte, no en pares sustitutos.
Sin embargo, sí asigna una nueva cadena, pero luego debe asignar una para pasar de Convert.ToBytetodos modos. Además, la solución proporcionada en la revisión asigna otro objeto más en cada iteración (la matriz de dos caracteres); puede colocar esa asignación de forma segura fuera del ciclo y reutilizar la matriz para evitar eso.
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;}
Cada hexadecimal numeralrepresenta un solo octeto con dos dígitos (símbolos).
Pero entonces, ¿por qué llamar StringReader.Readdos veces? Simplemente llame a su segunda sobrecarga y pídale que lea dos caracteres en la matriz de dos caracteres a la vez; y reduzca la cantidad de llamadas en dos.
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;}
Lo que te queda es un lector de cadenas cuyo único "valor" agregado es un índice paralelo (interno _pos) que podrías haber declarado (como, jpor ejemplo), una variable de longitud redundante (interna _length) y una referencia redundante a la entrada cadena (interna _s). En otras palabras, es inútil.
Si se pregunta cómo Read"lee", solo mire el código , todo lo que hace es llamar String.CopyToa la cadena de entrada. El resto es solo gastos generales de contabilidad para mantener valores que no necesitamos.
Por lo tanto, elimine el lector de cadenas y llámese CopyTousted mismo; Es más simple, más claro y más eficiente.
¿Realmente necesita un jíndice que incremente en pasos de dos paralelos a i? Por supuesto que no, simplemente multiplique ipor dos (que el compilador debería poder optimizar para una adición).
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;}
¿Cómo se ve la solución ahora? Exactamente como era al principio, solo que en lugar de usar String.Substringpara asignar la cadena y copiar los datos, está utilizando una matriz intermedia a la que copia los números hexadecimales, luego asigna la cadena usted mismo y copia los datos nuevamente desde la matriz y dentro de la cadena (cuando la pasa en el constructor de la cadena). La segunda copia podría optimizarse si la cadena ya está en el grupo interno, pero String.Substringtambién podrá evitarla en estos casos.
De hecho, si observa de String.Substringnuevo, verá que utiliza un conocimiento interno de bajo nivel de cómo se construyen las cadenas para asignar la cadena más rápido de lo que normalmente podría hacerlo, y alinea el mismo código utilizado CopyTodirectamente allí para evitar la llamada sobrecarga.
String.Substring
El peor de los casos: una asignación rápida, una copia rápida.
Mejor caso: sin asignación, sin copia.
Método manual
Peor de los casos: dos asignaciones normales, una copia normal, una copia rápida.
Mejor de los casos: una asignación normal, una copia normal.
¿Conclusión? Si desea usarConvert.ToByte(String, Int32) (porque no quiere volver a implementar esa funcionalidad usted mismo), no parece haber una manera de vencer String.Substring; todo lo que haces es correr en círculos, reinventando la rueda (solo con materiales subóptimos).
Tenga en cuenta que usar Convert.ToBytey String.Substringes una opción perfectamente válida si no necesita un rendimiento extremo. Recuerde: solo opte por una alternativa si tiene el tiempo y los recursos para investigar cómo funciona correctamente.
Si hubiera un Convert.ToByte(char[], Int32), las cosas serían diferentes, por supuesto (sería posible hacer lo que describí anteriormente y evitar por completo String).
Sospecho que las personas que informan un mejor rendimiento al "evitar String.Substring" también evitan Convert.ToByte(String, Int32), lo que realmente debería estar haciendo si necesita el rendimiento de todos modos. Mira las innumerables otras respuestas para descubrir todos los diferentes enfoques para hacerlo.
Descargo de responsabilidad: no he descompilado la última versión del marco para verificar que la fuente de referencia esté actualizada, supongo que sí.
Ahora, todo suena bien y lógico, con suerte incluso obvio si has logrado llegar tan lejos. Pero es verdad?
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
¡Si!
Apoyos de Partridge para el framework de banco, es fácil de hackear. La entrada utilizada es el siguiente hash SHA-1 que se repite 5000 veces para formar una cadena de 100,000 bytes de longitud.
209113288F93A9AB8E474EA78D899AFDBB874355
¡Que te diviertas! (Pero optimice con moderación).
error: {"No se pudo encontrar ningún dígito reconocible".}
Priya Jagtap
17
Complemento para responder por @CodesInChaos (método inverso)
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;}
Explicación:
& 0x0f es apoyar también letras minúsculas
hi = hi + 10 + ((hi >> 31) & 7); es lo mismo que:
hi = ch-65 + 10 + (((ch-65) >> 31) & 7);
Para '0' ... '9' es lo mismo que lo hi = ch - 65 + 10 + 7;que es hi = ch - 48(esto se debe a 0xffffffff & 7).
Para 'A' ... 'F' es hi = ch - 65 + 10;(esto se debe a 0x00000000 & 7).
Para 'a' ... 'f' tenemos números grandes, por lo que debemos restar 32 de la versión predeterminada haciendo algunos bits 0usando & 0x0f.
65 es código para 'A'
48 es código para '0'
7 es el número de letras entre '9'y 'A'en la tabla ASCII ( ...456789:;<=>?@ABCD...).
Este problema también podría resolverse utilizando una tabla de búsqueda. Esto requeriría una pequeña cantidad de memoria estática tanto para el codificador como para el decodificador. Sin embargo, este método será rápido:
Tabla de codificador 512 bytes o 1024 bytes (dos veces el tamaño si se necesitan mayúsculas y minúsculas)
Tabla de decodificadores de 256 bytes o 64 KiB (ya sea una búsqueda de un solo carácter o una búsqueda de dos caracteres)
Mi solución usa 1024 bytes para la tabla de codificación y 256 bytes para la decodificación.
Descodificación
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]));}
Codificación
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]];}
Durante la decodificación, IOException e IndexOutOfRangeException pueden ocurrir (si un carácter tiene un valor demasiado alto> 256). Deben implementarse métodos para descodificar flujos o matrices, esto es solo una prueba de concepto.
El uso de memoria de 256 bytes es insignificante cuando ejecuta código en el CLR.
dolmen
9
Esta es una gran publicación. Me gusta la solución de Waleed. No lo he pasado por la prueba de Patridge, pero parece ser bastante rápido. También necesitaba el proceso inverso, convirtiendo una cadena hexadecimal en una matriz de bytes, así que lo escribí como una inversión de la solución de Waleed. No estoy seguro si es más rápido que la solución original de Tomalak. Nuevamente, tampoco ejecuté el proceso inverso a través de la prueba 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;}
Este código supone que la cadena hexadecimal usa caracteres alfabéticos en mayúsculas y explota si la cadena hexadecimal usa letras minúsculas alfa. Es posible que desee hacer una conversión "mayúscula" en la cadena de entrada para estar seguro.
Marc Novakowski el
Esa es una observación astuta Marc. El código fue escrito para revertir la solución de Waleed. La llamada ToUpper ralentizaría un poco el algoritmo, pero le permitiría manejar caracteres alfabéticos en minúsculas.
Chris F
3
Convert.ToByte (topChar + bottomChar) se puede escribir como (byte) (topChar + bottomChar)
Amir Rezaei
Para manejar ambos casos sin una gran penalización de rendimiento,hexString[i] &= ~0x20;
Ben Voigt
9
¿Por qué hacerlo complejo? Esto es simple en Visual Studio 2008:
La razón es el rendimiento, cuando necesita una solución de alto rendimiento. :)
Ricky
7
No para apilar las muchas respuestas aquí, pero encontré una implementación bastante óptima (~ 4.5 veces mejor de lo aceptado), directa del analizador de cadenas hexadecimales. Primero, salida de mis pruebas (el primer lote es mi implementación):
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
Las líneas base64 y 'BitConverter'd' están ahí para probar la corrección. Tenga en cuenta que son iguales.
La implementación:
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;}
Intenté algunas cosas unsafey moví la ifsecuencia (claramente redundante) de personaje a mordisco a otro método, pero este fue el más rápido.
(Admito que esto responde la mitad de la pregunta. Sentí que la conversión cadena-> byte [] estaba subrepresentada, mientras que el ángulo de cadena byte [] -> parece estar bien cubierto. Por lo tanto, esta respuesta).
Para los seguidores de Knuth: hice esto porque necesito analizar algunos miles de cadenas hexadecimales cada pocos minutos, por lo que es importante que sea lo más rápido posible (en el bucle interno, por así decirlo). La solución de Tomalak no es notablemente más lenta si no se producen muchos de estos análisis.
Ben Mosher
5
Versiones seguras:
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;}}}
Versiones inseguras Para aquellos que prefieren el rendimiento y no temen a la inseguridad. Aproximadamente un 35% más rápido de ToHex y un 10% más rápido de FromHex.
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;}}}
Por cierto,
para las pruebas de referencia que inicializan el alfabeto cada vez que la función de conversión llamada es incorrecta, el alfabeto debe ser constante (para cadena) o solo lectura estática (para char []). Luego, la conversión alfabética de byte [] a cadena se vuelve tan rápida como las versiones de manipulación de byte.
Y, por supuesto, la prueba debe compilarse en la versión (con optimización) y con la opción de depuración "Suprimir optimización JIT" desactivada (lo mismo para "Habilitar solo mi código" si el código debe ser depurable).
Función inversa para el código 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;}
Función Waleed Eissa con soporte de minúsculas:
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étodos de extensión (descargo de responsabilidad: código completamente no probado, por cierto ...):
publicstaticclassByteExtensions{publicstaticstringToHexString(thisbyte[] ba){StringBuilder hex =newStringBuilder(ba.Length*2);foreach(byte b in ba){
hex.AppendFormat("{0:x2}", b);}return hex.ToString();}}
etc. Utilice cualquiera de las tres soluciones de Tomalak (siendo la última un método de extensión en una cadena).
Probablemente debería probar el código antes de ofrecerlo para una pregunta como esta.
jww
3
De los desarrolladores de Microsoft, una conversión simple y agradable:
publicstaticstringByteArrayToString(byte[] ba){// Concatenate the bytes into one long stringreturn ba.Aggregate(newStringBuilder(32),(sb, b)=> sb.Append(b.ToString("X2"))).ToString();}
Si bien lo anterior es limpio y compacto, los adictos al rendimiento gritarán al respecto usando enumeradores. Puede obtener el máximo rendimiento con una versión mejorada de la respuesta original 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();}
Esta es la más rápida de todas las rutinas que he visto publicadas aquí hasta ahora. No solo confíe en mi palabra ... pruebe el rendimiento de cada rutina e inspeccione su código CIL por usted mismo.
si Source == nullo Source.Length == 0tenemos un problema señor!
Andrei Krasutski
2
En términos de velocidad, esto parece ser mejor que nada aquí:
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);}
No obtuve el código que sugirió que funcionara, Olipro. hex[i] + hex[i+1]aparentemente devuelto un int.
Sin embargo, tuve cierto éxito al tomar algunas sugerencias del código de Waleeds y trabajar juntos. Es feo como el infierno, pero parece funcionar y funciona a 1/3 del tiempo en comparación con los demás de acuerdo con mis pruebas (usando el mecanismo de prueba de puentes). Dependiendo del tamaño de entrada. Cambiar alrededor de los?: S para separar 0-9 primero probablemente arrojaría un resultado un poco más rápido ya que hay más números que letras.
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;}
Esta versión de ByteArrayToHexViaByteManipulation podría ser más rápida.
De mis informes:
ByteArrayToHexViaByteManipulation3: 1,68 ticks promedio (más de 1000 carreras), 17,5X
ByteArrayToHexViaByteManipulation2: 1,73 ticks promedio (más de 1000 carreras), 16,9X
ByteArrayToHexViaByteManipulation: 2,90 ticks promedio (más de 1000 carreras), 10,1X
ByteArrayToHexViaLookupAndShift: 3,22 ticks promedio (más de 1000 carreras), 9,1X
...
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);}
Y creo que esta es una optimización:
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);}
Entraré en esta competencia de violín de bits, ya que tengo una respuesta que también usa bit-fiddling para decodificar hexadecimales. Tenga en cuenta que el uso de matrices de caracteres puede ser aún más rápido ya que los StringBuildermétodos de llamada también tomarán tiempo.
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, realmente debería optimizar esto Char[]y usarlo Charinternamente en lugar de ints ...
Maarten Bodewes
Para C #, es preferible inicializar las variables donde se usan, en lugar de fuera del ciclo, para permitir que el compilador se optimice. Obtengo un rendimiento equivalente de cualquier manera.
Peteter
2
Para el rendimiento, iría con la solución drphrozens. Una pequeña optimización para el decodificador podría ser usar una tabla para cualquiera de los caracteres para deshacerse del "<< 4".
Claramente, las dos llamadas al método son costosas. Si se realiza algún tipo de verificación en los datos de entrada o salida (podría ser CRC, suma de verificación o lo que sea), if (b == 255)...podría omitirse y, por lo tanto, también el método llama por completo.
Usar offset++y en offsetlugar de offsety offset + 1podría dar algún beneficio teórico, pero sospecho que el compilador maneja esto mejor que yo.
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]));}
Esto está justo en la parte superior de mi cabeza y no ha sido probado ni comparado.
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;}
Dos mashups que pliegan las dos operaciones de mordisco en una.
Probablemente versión bastante eficiente:
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 );}
Versión decadente de 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))));}
Y al revés:
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") devuelve 0x02 que es malo
CoperNick
1
Otra forma es usar stackallocpara reducir la presión de la memoria del 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);}
Aquí está mi oportunidad. He creado un par de clases de extensión para extender cadenas y bytes. En la prueba de archivos grandes, el rendimiento es comparable al Byte Manipulation 2.
El siguiente código para ToHexString es una implementación optimizada del algoritmo de búsqueda y cambio. Es casi idéntico al de Behrooz, pero resulta que usa un foreachpara iterar y un contador es más rápido que una indexación explícitafor .
Viene en segundo lugar detrás de Byte Manipulation 2 en mi máquina y es un código muy legible. Los siguientes resultados de la prueba también son de interés:
ToHexStringCharArrayWithCharArrayLookup: 41,589.69 ticks promedio (más de 1000 carreras), 1.5X ToHexStringCharArrayWithStringLookup: 50,764.06 ticks promedio (más de 1000 carreras), 1.2X ToHexStringStringBuilderWithCharArray12okup: 62 tj (62,87)
En base a los resultados anteriores, parece seguro concluir que:
Las penalizaciones por indexar en una cadena para realizar la búsqueda frente a una matriz de caracteres son significativas en la prueba de archivos grandes.
Las penalizaciones por usar un StringBuilder de capacidad conocida versus una matriz de caracteres de tamaño conocido para crear la cadena son aún más significativas.
Aquí está el código:
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;}}}
A continuación se muestran los resultados de las pruebas que obtuve cuando puse mi código en el proyecto de prueba de @ patridge en mi máquina. También agregué una prueba para convertir a una matriz de bytes de hexadecimal. Las pruebas que ejercieron mi código son ByteArrayToHexViaOptimizedLookupAndShift y HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte se tomó de XXXX. HexToByteArrayViaSoapHexBinary es el de la respuesta de @ Mykroft.
Respuestas:
Ya sea:
o:
Hay incluso más variantes de hacerlo, por ejemplo aquí .
La conversión inversa sería así:
Usar
Substring
es la mejor opción en combinación conConvert.ToByte
. Vea esta respuesta para más información. Si necesita un mejor rendimiento, debe evitarloConvert.ToByte
antes de poder caerSubString
.fuente
Análisis de rendimiento
Nota: nuevo líder a partir del 2015-08-20.
Ejecuté cada uno de los diversos métodos de conversión a través de algunas
Stopwatch
pruebas de rendimiento en bruto , una ejecución con una oración aleatoria (n = 61, 1000 iteraciones) y una ejecución con un texto del Proyecto Gutenburg (n = 1,238,957, 150 iteraciones). Aquí están los resultados, aproximadamente del más rápido al más lento. Todas las mediciones están en ticks ( 10,000 ticks = 1 ms ) y todas las notas relativas se comparan con laStringBuilder
implementación [más lenta] . Para el código utilizado, vea a continuación o el repositorio de marco de prueba donde ahora mantengo el código para ejecutar esto.Descargo de responsabilidad
ADVERTENCIA: No confíe en estas estadísticas para nada concreto; son simplemente una muestra de datos de muestra. Si realmente necesita un rendimiento de primer nivel, pruebe estos métodos en un entorno representativo de sus necesidades de producción con datos representativos de lo que utilizará.
Resultados
unsafe
(a través de CodesInChaos) (añadido a repo prueba por airbreather )BitConverter
(a través de Tomalak){SoapHexBinary}.ToString
(a través de Mykroft){byte}.ToString("X2")
(usandoforeach
) (derivado de la respuesta de Will Dean){byte}.ToString("X2")
(usando{IEnumerable}.Aggregate
, requiere System.Linq) (a través de Mark)Array.ConvertAll
(usandostring.Join
) (a través de Will Dean)Array.ConvertAll
(usandostring.Concat
, requiere .NET 4.0) (a través de Will Dean){StringBuilder}.AppendFormat
(usandoforeach
) (a través de Tomalak){StringBuilder}.AppendFormat
(usando{IEnumerable}.Aggregate
, requiere System.Linq) (derivado de la respuesta de Tomalak)Las tablas de búsqueda han tomado la delantera sobre la manipulación de bytes. Básicamente, hay alguna forma de precomputar lo que cualquier mordisco o byte dado estará en hexadecimal. Luego, mientras revisa los datos, simplemente busca la siguiente porción para ver qué cadena hexadecimal sería. Ese valor se agrega a la salida de cadena resultante de alguna manera. Durante mucho tiempo, la manipulación de bytes, potencialmente más difícil de leer por algunos desarrolladores, fue el enfoque de mayor rendimiento.
Su mejor opción será encontrar algunos datos representativos y probarlos en un entorno similar a la producción. Si tiene diferentes restricciones de memoria, puede preferir un método con menos asignaciones a uno que sea más rápido pero consuma más memoria.
Código de prueba
Siéntase libre de jugar con el código de prueba que utilicé. Aquí se incluye una versión, pero puede clonar el repositorio y agregar sus propios métodos. Envíe una solicitud de extracción si encuentra algo interesante o desea ayudar a mejorar el marco de prueba que utiliza.
Func<byte[], string>
) a /Tests/ConvertByteArrayToHexString/Test.cs.TestCandidates
valor de retorno en esa misma clase.GenerateTestInput
en esa misma clase.Actualización (2010-01-13)
Se agregó la respuesta de Waleed al análisis. Bastante rapido.
Actualización (2011-10-05)
string.Concat
Array.ConvertAll
Variante agregada para completar (requiere .NET 4.0). A la par destring.Join
versión.Actualización (2012-02-05)
El repositorio de prueba incluye más variantes como
StringBuilder.Append(b.ToString("X2"))
. Ninguno alteró los resultados.foreach
es más rápido que{IEnumerable}.Aggregate
, por ejemplo, peroBitConverter
aún así gana.Actualización (2012-04-03)
Se agregó la
SoapHexBinary
respuesta de Mykroft al análisis, que tomó el tercer lugar.Actualización (2013-01-15)
Se agregó la respuesta de manipulación de bytes de CodesInChaos, que ocupó el primer lugar (por un gran margen en grandes bloques de texto).
Actualización (2013-05-23)
Se agregó la respuesta de búsqueda de Nathan Moinvaziri y la variante del blog de Brian Lambert. Ambos bastante rápido, pero sin tomar la iniciativa en la máquina de prueba que utilicé (AMD Phenom 9750).
Actualización (2014-07-31)
Se agregó la nueva respuesta de búsqueda basada en bytes de @ CodesInChaos. Parece haber tomado la delantera tanto en las pruebas de oraciones como en las pruebas de texto completo.
Actualización (2015-08-20)
Se agregaron optimizaciones y
unsafe
variantes de airbreather al repositorio de esta respuesta . Si quieres jugar en el juego inseguro, puedes obtener grandes ganancias de rendimiento sobre cualquiera de los ganadores principales anteriores tanto en cadenas cortas como en textos grandes.fuente
bytes.ToHexStringAtLudicrousSpeed()
).Hay una clase llamada SoapHexBinary que hace exactamente lo que quieres.
fuente
Al escribir código criptográfico, es común evitar ramas dependientes de datos y búsquedas de tablas para garantizar que el tiempo de ejecución no dependa de los datos, ya que el tiempo dependiente de datos puede conducir a ataques de canal lateral.
También es bastante rápido.
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn
Una explicación del poco extraño violín:
bytes[i] >> 4
extrae el mordisco alto de un bytebytes[i] & 0xF
extrae el mordisco bajo de un byteb - 10
es
< 0
para los valoresb < 10
, que se convertirá en un dígito decimales
>= 0
para los valoresb > 10
, que se convertirán en una cartaA
aF
.i >> 31
de un entero de 32 bits con signo extrae el signo, gracias a la extensión de signo. Será-1
pori < 0
y0
parai >= 0
.(b-10)>>31
será0
para letras y-1
para dígitos.0
, yb
está en el rango de 10 a 15. Queremos asignarlo aA
(65) aF
(70), lo que implica sumar 55 ('A'-10
).b
del rango de 0 a 9 al rango0
(48) a9
(57). Esto significa que necesita convertirse en -7 ('0' - 55
).Ahora podríamos simplemente multiplicar por 7. Pero dado que -1 está representado por todos los bits que son 1, podemos usar
& -7
desde entonces(0 & -7) == 0
y(-1 & -7) == -7
.Algunas consideraciones adicionales:
c
, ya que la medición muestra que calcularlo desdei
es más barato.i < bytes.Length
como límite superior del bucle permite que el JITter elimine las comprobaciones de límitesbytes[i]
, por lo que elegí esa variante.b
un int permite conversiones innecesarias desde y hacia byte.fuente
hex string
parabyte[] array
?87 + b + (((b-10)>>31)&-39)
byte[] array
", que literalmente significa un conjunto de conjuntos de bytes, obyte[][]
. Solo me estaba burlando.Si desea más flexibilidad que
BitConverter
, pero no desea esos torpes bucles explícitos al estilo de la década de 1990, puede hacer lo siguiente:O, si está utilizando .NET 4.0:
(Esto último de un comentario en la publicación original).
fuente
Otro enfoque basado en la tabla de búsqueda. Éste usa solo una tabla de búsqueda para cada byte, en lugar de una tabla de búsqueda por mordisco.
También he probado variantes de este usando
ushort
,struct{char X1, X2}
,struct{byte X1, X2}
en la tabla de búsqueda.Dependiendo del objetivo de compilación (x86, X64), esos tenían aproximadamente el mismo rendimiento o eran ligeramente más lentos que esta variante.
Y para un rendimiento aún mayor, su
unsafe
hermano:O si considera aceptable escribir directamente en la cadena:
fuente
Span
se puede usar ahora en lugar deunsafe
??Puede usar el método BitConverter.ToString:
Salida:
Más información: BitConverter.ToString Method (Byte [])
fuente
Acabo de encontrar el mismo problema hoy, y me encontré con este código:
Fuente: Byte de publicación del foro [] Array to Hex String (ver la publicación de PZahra). Modifiqué un poco el código para eliminar el prefijo 0x.
Hice algunas pruebas de rendimiento del código y fue casi ocho veces más rápido que usar BitConverter.ToString () (el más rápido según la publicación de Patridge).
fuente
Esta es una respuesta a la revisión 4 de la respuesta muy popular de Tomalak (y las ediciones posteriores).
Explicaré que esta edición es incorrecta y explicaré por qué podría revertirse. En el camino, es posible que aprenda una o dos cosas sobre algunas partes internas y vea otro ejemplo más de lo que realmente es la optimización prematura y cómo puede morderlo.
tl; dr: simplemente use
Convert.ToByte
yString.Substring
si tiene prisa ("Código original" a continuación), es la mejor combinación si no desea volver a implementarConvert.ToByte
. Use algo más avanzado (vea otras respuestas) que no se usaConvert.ToByte
si necesita rendimiento. No , no utilizar ninguna otra cosa que no seaString.Substring
en combinación conConvert.ToByte
, a menos que alguien tiene algo interesante que decir acerca de esto en los comentarios de esta respuesta.advertencia: esta respuesta puede volverse obsoleta si un
Convert.ToByte(char[], Int32)
se implementa sobrecarga en el marco. Es poco probable que esto suceda pronto.Como regla general, no me gusta mucho decir "no optimices prematuramente", porque nadie sabe cuándo es "prematuro". Lo único que debe tener en cuenta al decidir si optimizar o no es: "¿Tengo el tiempo y los recursos para investigar los enfoques de optimización correctamente?". Si no lo hace, entonces es demasiado pronto, espere hasta que su proyecto es más maduro o hasta que necesite el rendimiento (si hay una necesidad real, entonces usted va a hacer que el tiempo). Mientras tanto, haga lo más simple que podría funcionar en su lugar.
Código original:
Revisión 4:
La revisión evita
String.Substring
y utiliza unStringReader
en su lugar. La razón dada es:Bueno, mirando el código de referencia
String.Substring
, ya es claramente "de un solo paso"; y por qué no debería ser? Funciona a nivel de byte, no en pares sustitutos.Sin embargo, sí asigna una nueva cadena, pero luego debe asignar una para pasar de
Convert.ToByte
todos modos. Además, la solución proporcionada en la revisión asigna otro objeto más en cada iteración (la matriz de dos caracteres); puede colocar esa asignación de forma segura fuera del ciclo y reutilizar la matriz para evitar eso.Cada hexadecimal
numeral
representa un solo octeto con dos dígitos (símbolos).Pero entonces, ¿por qué llamar
StringReader.Read
dos veces? Simplemente llame a su segunda sobrecarga y pídale que lea dos caracteres en la matriz de dos caracteres a la vez; y reduzca la cantidad de llamadas en dos.Lo que te queda es un lector de cadenas cuyo único "valor" agregado es un índice paralelo (interno
_pos
) que podrías haber declarado (como,j
por ejemplo), una variable de longitud redundante (interna_length
) y una referencia redundante a la entrada cadena (interna_s
). En otras palabras, es inútil.Si se pregunta cómo
Read
"lee", solo mire el código , todo lo que hace es llamarString.CopyTo
a la cadena de entrada. El resto es solo gastos generales de contabilidad para mantener valores que no necesitamos.Por lo tanto, elimine el lector de cadenas y llámese
CopyTo
usted mismo; Es más simple, más claro y más eficiente.¿Realmente necesita un
j
índice que incremente en pasos de dos paralelos ai
? Por supuesto que no, simplemente multipliquei
por dos (que el compilador debería poder optimizar para una adición).¿Cómo se ve la solución ahora? Exactamente como era al principio, solo que en lugar de usar
String.Substring
para asignar la cadena y copiar los datos, está utilizando una matriz intermedia a la que copia los números hexadecimales, luego asigna la cadena usted mismo y copia los datos nuevamente desde la matriz y dentro de la cadena (cuando la pasa en el constructor de la cadena). La segunda copia podría optimizarse si la cadena ya está en el grupo interno, peroString.Substring
también podrá evitarla en estos casos.De hecho, si observa de
String.Substring
nuevo, verá que utiliza un conocimiento interno de bajo nivel de cómo se construyen las cadenas para asignar la cadena más rápido de lo que normalmente podría hacerlo, y alinea el mismo código utilizadoCopyTo
directamente allí para evitar la llamada sobrecarga.String.Substring
Método manual
¿Conclusión? Si desea usar
Convert.ToByte(String, Int32)
(porque no quiere volver a implementar esa funcionalidad usted mismo), no parece haber una manera de vencerString.Substring
; todo lo que haces es correr en círculos, reinventando la rueda (solo con materiales subóptimos).Tenga en cuenta que usar
Convert.ToByte
yString.Substring
es una opción perfectamente válida si no necesita un rendimiento extremo. Recuerde: solo opte por una alternativa si tiene el tiempo y los recursos para investigar cómo funciona correctamente.Si hubiera un
Convert.ToByte(char[], Int32)
, las cosas serían diferentes, por supuesto (sería posible hacer lo que describí anteriormente y evitar por completoString
).Sospecho que las personas que informan un mejor rendimiento al "evitar
String.Substring
" también evitanConvert.ToByte(String, Int32)
, lo que realmente debería estar haciendo si necesita el rendimiento de todos modos. Mira las innumerables otras respuestas para descubrir todos los diferentes enfoques para hacerlo.Descargo de responsabilidad: no he descompilado la última versión del marco para verificar que la fuente de referencia esté actualizada, supongo que sí.
Ahora, todo suena bien y lógico, con suerte incluso obvio si has logrado llegar tan lejos. Pero es verdad?
¡Si!
Apoyos de Partridge para el framework de banco, es fácil de hackear. La entrada utilizada es el siguiente hash SHA-1 que se repite 5000 veces para formar una cadena de 100,000 bytes de longitud.
¡Que te diviertas! (Pero optimice con moderación).
fuente
Complemento para responder por @CodesInChaos (método inverso)
Explicación:
& 0x0f
es apoyar también letras minúsculashi = hi + 10 + ((hi >> 31) & 7);
es lo mismo que:hi = ch-65 + 10 + (((ch-65) >> 31) & 7);
Para '0' ... '9' es lo mismo que lo
hi = ch - 65 + 10 + 7;
que eshi = ch - 48
(esto se debe a0xffffffff & 7
).Para 'A' ... 'F' es
hi = ch - 65 + 10;
(esto se debe a0x00000000 & 7
).Para 'a' ... 'f' tenemos números grandes, por lo que debemos restar 32 de la versión predeterminada haciendo algunos bits
0
usando& 0x0f
.65 es código para
'A'
48 es código para
'0'
7 es el número de letras entre
'9'
y'A'
en la tabla ASCII (...456789:;<=>?@ABCD...
).fuente
Este problema también podría resolverse utilizando una tabla de búsqueda. Esto requeriría una pequeña cantidad de memoria estática tanto para el codificador como para el decodificador. Sin embargo, este método será rápido:
Mi solución usa 1024 bytes para la tabla de codificación y 256 bytes para la decodificación.
Descodificación
Codificación
Comparación
* esta solución
Nota
Durante la decodificación, IOException e IndexOutOfRangeException pueden ocurrir (si un carácter tiene un valor demasiado alto> 256). Deben implementarse métodos para descodificar flujos o matrices, esto es solo una prueba de concepto.
fuente
Esta es una gran publicación. Me gusta la solución de Waleed. No lo he pasado por la prueba de Patridge, pero parece ser bastante rápido. También necesitaba el proceso inverso, convirtiendo una cadena hexadecimal en una matriz de bytes, así que lo escribí como una inversión de la solución de Waleed. No estoy seguro si es más rápido que la solución original de Tomalak. Nuevamente, tampoco ejecuté el proceso inverso a través de la prueba de Patridge.
fuente
hexString[i] &= ~0x20;
¿Por qué hacerlo complejo? Esto es simple en Visual Studio 2008:
C#:
VB:
fuente
No para apilar las muchas respuestas aquí, pero encontré una implementación bastante óptima (~ 4.5 veces mejor de lo aceptado), directa del analizador de cadenas hexadecimales. Primero, salida de mis pruebas (el primer lote es mi implementación):
Las líneas base64 y 'BitConverter'd' están ahí para probar la corrección. Tenga en cuenta que son iguales.
La implementación:
Intenté algunas cosas
unsafe
y moví laif
secuencia (claramente redundante) de personaje a mordisco a otro método, pero este fue el más rápido.(Admito que esto responde la mitad de la pregunta. Sentí que la conversión cadena-> byte [] estaba subrepresentada, mientras que el ángulo de cadena byte [] -> parece estar bien cubierto. Por lo tanto, esta respuesta).
fuente
Versiones seguras:
Versiones inseguras Para aquellos que prefieren el rendimiento y no temen a la inseguridad. Aproximadamente un 35% más rápido de ToHex y un 10% más rápido de FromHex.
Por cierto, para las pruebas de referencia que inicializan el alfabeto cada vez que la función de conversión llamada es incorrecta, el alfabeto debe ser constante (para cadena) o solo lectura estática (para char []). Luego, la conversión alfabética de byte [] a cadena se vuelve tan rápida como las versiones de manipulación de byte.
Y, por supuesto, la prueba debe compilarse en la versión (con optimización) y con la opción de depuración "Suprimir optimización JIT" desactivada (lo mismo para "Habilitar solo mi código" si el código debe ser depurable).
fuente
Función inversa para el código Waleed Eissa (Hex String To Byte Array):
Función Waleed Eissa con soporte de minúsculas:
fuente
Métodos de extensión (descargo de responsabilidad: código completamente no probado, por cierto ...):
etc. Utilice cualquiera de las tres soluciones de Tomalak (siendo la última un método de extensión en una cadena).
fuente
De los desarrolladores de Microsoft, una conversión simple y agradable:
Si bien lo anterior es limpio y compacto, los adictos al rendimiento gritarán al respecto usando enumeradores. Puede obtener el máximo rendimiento con una versión mejorada de la respuesta original de Tomalak :
Esta es la más rápida de todas las rutinas que he visto publicadas aquí hasta ahora. No solo confíe en mi palabra ... pruebe el rendimiento de cada rutina e inspeccione su código CIL por usted mismo.
fuente
b.ToSting("X2")
.Y para insertar en una cadena SQL (si no está utilizando parámetros de comando):
fuente
Source == null
oSource.Length == 0
tenemos un problema señor!En términos de velocidad, esto parece ser mejor que nada aquí:
fuente
No obtuve el código que sugirió que funcionara, Olipro.
hex[i] + hex[i+1]
aparentemente devuelto unint
.Sin embargo, tuve cierto éxito al tomar algunas sugerencias del código de Waleeds y trabajar juntos. Es feo como el infierno, pero parece funcionar y funciona a 1/3 del tiempo en comparación con los demás de acuerdo con mis pruebas (usando el mecanismo de prueba de puentes). Dependiendo del tamaño de entrada. Cambiar alrededor de los?: S para separar 0-9 primero probablemente arrojaría un resultado un poco más rápido ya que hay más números que letras.
fuente
Esta versión de ByteArrayToHexViaByteManipulation podría ser más rápida.
De mis informes:
...
Y creo que esta es una optimización:
fuente
Entraré en esta competencia de violín de bits, ya que tengo una respuesta que también usa bit-fiddling para decodificar hexadecimales. Tenga en cuenta que el uso de matrices de caracteres puede ser aún más rápido ya que los
StringBuilder
métodos de llamada también tomarán tiempo.Convertido de código Java.
fuente
Char[]
y usarloChar
internamente en lugar de ints ...Para el rendimiento, iría con la solución drphrozens. Una pequeña optimización para el decodificador podría ser usar una tabla para cualquiera de los caracteres para deshacerse del "<< 4".
Claramente, las dos llamadas al método son costosas. Si se realiza algún tipo de verificación en los datos de entrada o salida (podría ser CRC, suma de verificación o lo que sea),
if (b == 255)...
podría omitirse y, por lo tanto, también el método llama por completo.Usar
offset++
y enoffset
lugar deoffset
yoffset + 1
podría dar algún beneficio teórico, pero sospecho que el compilador maneja esto mejor que yo.Esto está justo en la parte superior de mi cabeza y no ha sido probado ni comparado.
fuente
Otra variación más para la diversidad:
fuente
No está optimizado para la velocidad, pero tiene más LINQy que la mayoría de las respuestas (.NET 4.0):
fuente
Dos mashups que pliegan las dos operaciones de mordisco en una.
Probablemente versión bastante eficiente:
Versión decadente de linq-with-bit-hacking:
Y al revés:
fuente
Otra forma es usar
stackalloc
para reducir la presión de la memoria del GC:fuente
Aquí está mi oportunidad. He creado un par de clases de extensión para extender cadenas y bytes. En la prueba de archivos grandes, el rendimiento es comparable al Byte Manipulation 2.
El siguiente código para ToHexString es una implementación optimizada del algoritmo de búsqueda y cambio. Es casi idéntico al de Behrooz, pero resulta que usa un
foreach
para iterar y un contador es más rápido que una indexación explícitafor
.Viene en segundo lugar detrás de Byte Manipulation 2 en mi máquina y es un código muy legible. Los siguientes resultados de la prueba también son de interés:
ToHexStringCharArrayWithCharArrayLookup: 41,589.69 ticks promedio (más de 1000 carreras), 1.5X ToHexStringCharArrayWithStringLookup: 50,764.06 ticks promedio (más de 1000 carreras), 1.2X ToHexStringStringBuilderWithCharArray12okup: 62 tj (62,87)
En base a los resultados anteriores, parece seguro concluir que:
Aquí está el código:
A continuación se muestran los resultados de las pruebas que obtuve cuando puse mi código en el proyecto de prueba de @ patridge en mi máquina. También agregué una prueba para convertir a una matriz de bytes de hexadecimal. Las pruebas que ejercieron mi código son ByteArrayToHexViaOptimizedLookupAndShift y HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte se tomó de XXXX. HexToByteArrayViaSoapHexBinary es el de la respuesta de @ Mykroft.
fuente
Otra función rápida ...
fuente