c# - example - ¿Cómo convertir una matriz de bytes a una cadena hexadecimal y viceversa?
hex string to int c# (30)
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 pruebas de rendimiento de Stopwatch
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 de más rápido a más lento. Todas las mediciones están en tics ( 10,000 tics = 1 ms ) y todas las notas relativas se comparan con la implementación de StringBuilder
[más lenta]. Para el código utilizado, vea a continuación o el repositorio de prueba de marco donde ahora mantengo el código para ejecutar esto.
Renuncia
ADVERTENCIA: No confíe en estas estadísticas para nada concreto; son simplemente una muestra de datos de muestra. Si realmente necesita un rendimiento excelente, pruebe estos métodos en un entorno representativo de sus necesidades de producción con datos representativos de lo que usará.
Resultados
- Búsqueda por byte
unsafe
(a través de CodesInChaos) (agregada a prueba de repo por airbreather )- Texto: 4,727.85 (105.2X)
- Sentencia: 0.28 (99.7X)
- Búsqueda por byte (a través de CodesInChaos)
- Texto: 10,853.96 (45.8X más rápido)
- Sentencia: 0.65 (42.7X más rápido)
- Manipulación de bytes 2 (a través de CodesInChaos)
- Texto: 12,967.69 (38.4X más rápido)
- Sentencia: 0.73 (37.9X más rápido)
- Manipulación de bytes (a través de Waleed Eissa)
- Texto: 16,856.64 (29.5X más rápido)
- Sentencia: 0.70 (39.5X más rápido)
- Búsqueda / cambio (a través de Nathan Moinvaziri)
- Texto: 23,201.23 (21.4X más rápido)
- Sentencia: 1.24 (22.3X más rápido)
- Búsqueda por mordisco (a través de Brian Lambert)
- Texto: 23,879.41 (20.8X más rápido)
- Sentencia: 1.15 (23.9X más rápido)
-
BitConverter
(a través de Tomalak)- Texto: 113,269.34 (4.4X más rápido)
- Sentencia: 9.98 (2.8X más rápido)
-
{SoapHexBinary}.ToString
(a través de Mykroft)- Texto: 178,601.39 (2.8X más rápido)
- Sentencia: 10.68 (2.6X más rápido)
-
{byte}.ToString("X2")
(usandoforeach
) (derivado de la respuesta de Will Dean)- Texto: 308,805.38 (2.4X más rápido)
- Sentencia: 16.89 (2.4X más rápido)
-
{byte}.ToString("X2")
(usando{IEnumerable}.Aggregate
, requiere System.Linq) (vía Mark)- Texto: 352,828.20 (2.1X más rápido)
- Sentencia: 16.87 (2.4X más rápido)
-
Array.ConvertAll
(usandostring.Join
) (a través de Will Dean)- Texto: 675,451.57 (1.1X más rápido)
- Sentencia: 17.95 (2.2X más rápido)
-
Array.ConvertAll
(usandostring.Concat
, requiere .NET 4.0) (a través de Will Dean)- Texto: 752,078.70 (1.0X más rápido)
- Sentencia: 18.28 (2.2X más rápido)
-
{StringBuilder}.AppendFormat
(utilizandoforeach
) (a través de Tomalak)- Texto: 672,115.77 (1.1X más rápido)
- Sentencia: 36.82 (1.1X más rápido)
-
{StringBuilder}.AppendFormat
(usando{IEnumerable}.Aggregate
, requiere System.Linq) (derivado de la respuesta de Tomalak)- Texto: 718,380.63 (1.0X más rápido)
- Sentencia: 39.71 (1.0X más rápido)
Las tablas de búsqueda han tomado la ventaja sobre la manipulación de bytes. Básicamente, hay alguna forma de precomputar lo que cualquier nibble o byte dado estará en hexadecimal. Luego, a medida que revisa los datos, simplemente busque la siguiente parte 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 apuesta será encontrar algunos datos representativos y probarlos en un entorno de 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 que consuma más memoria.
Código de prueba
Siéntete libre de jugar con el código de prueba que usé. Aquí se incluye una versión, pero siéntase libre de clonar el repositorio y agregar sus propios métodos. Envíe una solicitud de extracción si encuentra algo interesante o si 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 valor de retorno de
TestCandidates
en esa misma clase. - Asegúrese de que está ejecutando la versión de entrada que desea, oración o texto, alternando los comentarios en
GenerateTestInput
en esa misma clase. - Presione F5 y espere la salida (también se genera un volcado de HTML en la carpeta / bin).
static string ByteArrayToHexStringViaStringJoinArrayConvertAll(byte[] bytes) {
return string.Join(string.Empty, Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaStringConcatArrayConvertAll(byte[] bytes) {
return string.Concat(Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaBitConverter(byte[] bytes) {
string hex = BitConverter.ToString(bytes);
return hex.Replace("-", "");
}
static string ByteArrayToHexStringViaStringBuilderAggregateByteToString(byte[] bytes) {
return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.Append(b.ToString("X2"))).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachByteToString(byte[] bytes) {
StringBuilder hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
hex.Append(b.ToString("X2"));
return hex.ToString();
}
static string ByteArrayToHexStringViaStringBuilderAggregateAppendFormat(byte[] bytes) {
return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.AppendFormat("{0:X2}", b)).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachAppendFormat(byte[] bytes) {
StringBuilder hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
hex.AppendFormat("{0:X2}", b);
return hex.ToString();
}
static string ByteArrayToHexViaByteManipulation(byte[] bytes) {
char[] c = new char[bytes.Length * 2];
byte b;
for (int i = 0; i < bytes.Length; i++) {
b = ((byte)(bytes[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(bytes[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
static string ByteArrayToHexViaByteManipulation2(byte[] bytes) {
char[] c = new char[bytes.Length * 2];
int b;
for (int i = 0; i < bytes.Length; i++) {
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
}
return new string(c);
}
static string ByteArrayToHexViaSoapHexBinary(byte[] bytes) {
SoapHexBinary soapHexBinary = new SoapHexBinary(bytes);
return soapHexBinary.ToString();
}
static string ByteArrayToHexViaLookupAndShift(byte[] bytes) {
StringBuilder result = new StringBuilder(bytes.Length * 2);
string hexAlphabet = "0123456789ABCDEF";
foreach (byte b in bytes) {
result.Append(hexAlphabet[(int)(b >> 4)]);
result.Append(hexAlphabet[(int)(b & 0xF)]);
}
return result.ToString();
}
static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_Lookup32, GCHandleType.Pinned).AddrOfPinnedObject();
static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes) {
var lookupP = _lookup32UnsafeP;
var result = new string((char)0, bytes.Length * 2);
fixed (byte* bytesP = bytes)
fixed (char* resultP = result) {
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++) {
resultP2[i] = lookupP[bytesP[i]];
}
}
return result;
}
static uint[] _Lookup32 = Enumerable.Range(0, 255).Select(i => {
string s = i.ToString("X2");
return ((uint)s[0]) + ((uint)s[1] << 16);
}).ToArray();
static string ByteArrayToHexViaLookupPerByte(byte[] bytes) {
var result = new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
var val = _Lookup32[bytes[i]];
result[2*i] = (char)val;
result[2*i + 1] = (char) (val >> 16);
}
return new string(result);
}
static string ByteArrayToHexViaLookup(byte[] bytes) {
string[] hexStringTable = new string[] {
"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F",
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F",
"20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F",
"30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F",
"40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F",
"50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F",
"60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F",
"70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F",
"80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F",
"90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F",
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF",
"B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF",
"C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF",
"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF",
"E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF",
"F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF",
};
StringBuilder result = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes) {
result.Append(hexStringTable[b]);
}
return result.ToString();
}
Actualización (2010-01-13)
Se agregó la respuesta de Waleed al análisis. Bastante rapido.
Actualización (2011-10-05)
Se string.Concat
variante string.Concat
Array.ConvertAll
para completar (requiere .NET 4.0). A la par con string.Join
version.
Actualización (2012-02-05)
El repositorio de prueba incluye más variantes, como StringBuilder.Append(b.ToString("X2"))
. Ninguno alteró los resultados ninguno. foreach
es más rápido que {IEnumerable}.Aggregate
, por ejemplo, pero BitConverter
aún gana.
Actualización (2012-04-03)
Se agregó la respuesta de Mykroft''s SoapHexBinary
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 tomó el primer lugar (por un amplio 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 son bastante rápidos, pero no tomaron 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 iniciativa tanto en las pruebas de oraciones como en las de texto completo.
Actualización (2015-08-20)
Se airbreather optimizaciones airbreather y una variante unsafe
al repositorio de esta respuesta . Si quieres jugar en el juego inseguro, puedes obtener grandes ganancias de rendimiento sobre cualquiera de los ganadores anteriores de cadenas cortas y textos grandes.
¿Cómo puedes convertir una matriz de bytes en una cadena hexadecimal y viceversa?
Acabo de encontrar el mismo problema hoy, y encontré este código:
private static string ByteArrayToHex(byte[] barray)
{
char[] c = new char[barray.Length * 2];
byte b;
for (int i = 0; i < barray.Length; ++i)
{
b = ((byte)(barray[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
Fuente: byte [] del foro [] Array to Hex String (ver el post 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 usando BitConverter.ToString () (el más rápido según la publicación de patridge).
Cuando se escribe código criptográfico, es común evitar las bifurcaciones dependientes de los datos y las búsquedas de tablas para garantizar que el tiempo de ejecución no dependa de los datos, ya que la temporización dependiente de los datos puede llevar a ataques de canal lateral.
También es bastante rápido.
static string ByteToHexBitFiddle(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
int b;
for (int i = 0; i < bytes.Length; i++) {
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b-10)>>31)&-7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string(c);
}
Ph''nglui mglw''nafh Cthulhu R''lyeh wgah''nagl fhtagn
Abandona toda esperanza, vosotros que entráis aquí.
Una explicación de lo extraño poco jugueteando:
-
bytes[i] >> 4
extrae el mordisco alto de un byte
bytes[i] & 0xF
extrae el nibble bajo de un byte -
b - 10
es< 0
para los valoresb < 10
, que se convertirá en un dígito decimal
es>= 0
para los valoresb > 10
, que se convertirá en una letra de laA
a laF
- El uso de
i >> 31
en un entero de 32 bits con signo extrae el signo, gracias a la extensión de signo. Será-1
parai < 0
y0
parai >= 0
. - Combinando 2) y 3), muestra que
(b-10)>>31
será0
para letras y-1
para dígitos. - Mirando el caso de las letras, el último sumando se convierte en
0
, yb
está en el rango de 10 a 15. Queremos asignarlo aA
(65) aF
(70), lo que implica agregar 55 (''A''-10
) . - Mirando el caso de los dígitos, queremos adaptar el último sumando para que asigne
b
del rango de 0 a 9 al rango de0
(48) a9
(57). Esto significa que debe convertirse en -7 (''0'' - 55
).
Ahora podríamos simplemente multiplicar con 7. Pero como -1 está representado por todos los bits que son 1, en su lugar podemos usar& -7
desde(0 & -7) == 0
y(-1 & -7) == -7
.
Algunas consideraciones adicionales:
- No utilicé una segunda variable de bucle para indexar en
c
, ya que la medición muestra que calcularlo a partir dei
es más barato. - Usar exactamente
i < bytes.Length
como límite superior del bucle permite al JITter eliminar las comprobaciones de límites en losbytes[i]
, así que elegí esa variante. - Hacer
b
an int permite conversiones innecesarias desde y hacia bytes.
Esta es una respuesta a la revisión 4 de share de share (y ediciones posteriores).
Haré caso de que esta edición sea incorrecta y explicaré por qué podría revertirse. A lo largo del camino, podría aprender una o dos cosas sobre algunos aspectos internos y ver otro ejemplo de lo que realmente es la optimización prematura y cómo puede morderlo.
tl; dr: Solo use Convert.ToByte
y String.Substring
si 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 (ver otras respuestas) que no use Convert.ToByte
si necesita rendimiento. No use nada más que String.Substring
en combinación con Convert.ToByte
, a menos que alguien tenga algo interesante que decir sobre esto en los comentarios de esta respuesta.
advertencia: esta respuesta puede volverse obsoleta si se Convert.ToByte(char[], Int32)
una Convert.ToByte(char[], Int32)
en el marco. Es poco probable que esto suceda pronto.
Como regla general, no me gusta mucho decir "no optimizar 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 sea más maduro o hasta que necesite el rendimiento (si existe una necesidad real, ganará tiempo). Mientras tanto, haga lo más simple que podría funcionar en su lugar.
Código original:
public static byte[] HexadecimalStringToByteArray_Original(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
for (var i = 0; i < outputLength; i++)
output[i] = Convert.ToByte(input.Substring(i * 2, 2), 16);
return output;
}
Revisión 4:
public static byte[] HexadecimalStringToByteArray_Rev4(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
output[i] = Convert.ToByte(new string(new char[2] { (char)sr.Read(), (char)sr.Read() }), 16);
}
return output;
}
La revisión evita String.Substring
y usa un StringReader
en StringReader
lugar. La razón dada es:
Edición: puede mejorar el rendimiento de cadenas largas mediante el uso de un analizador de un solo paso, así:
Bueno, mirando el código de referencia para String.Substring
, ya está claramente "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 de todos modos debe asignar una para pasarla a Convert.ToByte
. Además, la solución provista en la revisión asigna otro objeto en cada iteración (la matriz de dos caracteres); puede poner esa asignación de manera segura fuera del bucle y reutilizar la matriz para evitar eso.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
{
numeral[0] = (char)sr.Read();
numeral[1] = (char)sr.Read();
output[i] = Convert.ToByte(new string(numeral), 16);
}
}
return output;
}
Cada numeral
hexadecimal representa un solo octeto utilizando dos dígitos (símbolos).
Pero entonces, ¿por qué llamar a StringReader.Read
dos veces? Simplemente llame a su segunda sobrecarga y pídale que lea dos caracteres de la matriz de dos caracteres a la vez; y reducir la cantidad de llamadas en dos.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
{
var read = sr.Read(numeral, 0, 2);
Debug.Assert(read == 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
}
return output;
}
Lo que queda es un lector de cadenas cuyo único "valor" agregado es un índice paralelo (interno _pos
) que podría haberse 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 llamar String.CopyTo
a la cadena de entrada. El resto son solo gastos generales de contabilidad para mantener los valores que no necesitamos.
Entonces, elimina el lector de cuerdas y llámate a CopyTo
ti mismo; Es más simple, más claro y más eficiente.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
for (int i = 0, j = 0; i < outputLength; i++, j += 2)
{
input.CopyTo(j, numeral, 0, 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
return output;
}
¿Realmente necesitas un j
índice que se incremente en pasos de dos paralelos i
? Por supuesto que no, solo multiplica i
por dos (lo que el compilador debería poder optimizar para una adición)
public static byte[] HexadecimalStringToByteArray_BestEffort(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
for (int i = 0; i < outputLength; i++)
{
input.CopyTo(i * 2, numeral, 0, 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
return output;
}
¿Cómo se ve la solución ahora? Exactamente como era al principio, solo que en lugar de usarlo String.Substring
para asignar la cadena y copiarle los datos, está utilizando una matriz intermedia a la que copia los números hexadecimales, luego asigna la cadena usted mismo y vuelve a copiar los datos de la matriz y en la cadena (cuando se pasa en el constructor de cadena). La segunda copia podría optimizarse si la cadena ya está en el grupo interno, pero String.Substring
también podrá evitarla en estos casos.
De hecho, si miras de String.Substring
nuevo, verás que utiliza un conocimiento interno de bajo nivel sobre cómo se construyen las cadenas para asignar la cadena más rápido de lo que normalmente podrías hacerlo, e incluye el mismo código utilizado CopyTo
directamente allí para evitar la sobrecarga de llamadas.
String.Substring
- El peor de los casos: una asignación rápida, una copia rápida.
- El mejor de los casos: sin asignación, sin copia.
Método manual
- El peor de los casos: dos asignaciones normales, una copia normal, una copia rápida.
- Mejor caso: una asignación normal, una copia normal.
¿Conclusión? Si quieres usarConvert.ToByte(String, Int32)
(porque no quieres volver a implementar esa funcionalidad por ti mismo), no parece haber una manera de vencer String.Substring
; todo lo que hace es correr en círculos, reinventando la rueda (solo con materiales subóptimos).
Tenga en cuenta que usar Convert.ToByte
y String.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.
Por Convert.ToByte(char[], Int32)
supuesto, si hubiera una , las cosas serían diferentes (sería posible hacer lo que describí anteriormente y evitar por completo String
).
Sospecho que las personas que reportan 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. Mire las innumerables respuestas para descubrir 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 parece bueno y lógico, con suerte incluso obvio si has logrado llegar tan lejos. ¿Pero es verdad?
Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz
Cores: 8
Current Clock Speed: 2600
Max Clock Speed: 2600
--------------------
Parsing hexadecimal string into an array of bytes
--------------------
HexadecimalStringToByteArray_Original: 7,777.09 average ticks (over 10000 runs), 1.2X
HexadecimalStringToByteArray_BestEffort: 8,550.82 average ticks (over 10000 runs), 1.1X
HexadecimalStringToByteArray_Rev4: 9,218.03 average ticks (over 10000 runs), 1.0X
¡Sí!
Apoyos a Partridge para el marco del banco, es fácil de hackear. La entrada utilizada es el siguiente hash SHA-1 repetido 5000 veces para formar una cadena de 100,000 bytes.
209113288F93A9AB8E474EA78D899AFDBB874355
¡Que te diviertas! (Pero optimizar con moderación.)
Este problema también podría resolverse utilizando una tabla de consulta. Esto requeriría una pequeña cantidad de memoria estática tanto para el codificador como para el decodificador. Este método sin embargo será rápido:
- Encoder tabla 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 doble carácter)
Mi solución utiliza 1024 bytes para la tabla de codificación y 256 bytes para la decodificación.
Descodificación
private static readonly byte[] LookupTable = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static byte Lookup(char c)
{
var b = LookupTable[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
public static byte ToByte(char[] chars, int offset)
{
return (byte)(Lookup(chars[offset]) << 4 | Lookup(chars[offset + 1]));
}
Codificación
private static readonly char[][] LookupTableUpper;
private static readonly char[][] LookupTableLower;
static Hex()
{
LookupTableLower = new char[256][];
LookupTableUpper = new char[256][];
for (var i = 0; i < 256; i++)
{
LookupTableLower[i] = i.ToString("x2").ToCharArray();
LookupTableUpper[i] = i.ToString("X2").ToCharArray();
}
}
public static char[] ToCharLower(byte[] b, int bOffset)
{
return LookupTableLower[b[bOffset]];
}
public static char[] ToCharUpper(byte[] b, int bOffset)
{
return LookupTableUpper[b[bOffset]];
}
Comparación
StringBuilderToStringFromBytes: 106148
BitConverterToStringFromBytes: 15783
ArrayConvertAllToStringFromBytes: 54290
ByteManipulationToCharArray: 8444
TableBasedToCharArray: 5651 *
* esta solución
Nota
Durante la decodificación, se puede producir IOException y IndexOutOfRangeException (si un carácter tiene un valor demasiado alto> 256). Deben implementarse métodos para de / codificar flujos o matrices, esto es solo una prueba de concepto.
Hay una clase llamada SoapHexBinary que hace exactamente lo que quieres.
using System.Runtime.Remoting.Metadata.W3cXsd2001;
public static byte[] GetStringToBytes(string value)
{
SoapHexBinary shb = SoapHexBinary.Parse(value);
return shb.Value;
}
public static string GetBytesToString(byte[] value)
{
SoapHexBinary shb = new SoapHexBinary(value);
return shb.ToString();
}
Otro enfoque basado en la tabla de búsqueda. Esta utiliza solo una tabla de búsqueda para cada byte, en lugar de una tabla de búsqueda por nibble.
private static readonly uint[] _lookup32 = CreateLookup32();
private static uint[] CreateLookup32()
{
var result = new uint[256];
for (int i = 0; i < 256; i++)
{
string s=i.ToString("X2");
result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
}
return result;
}
private static string ByteArrayToHexViaLookup32(byte[] bytes)
{
var lookup32 = _lookup32;
var result = new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
var val = lookup32[bytes[i]];
result[2*i] = (char)val;
result[2*i + 1] = (char) (val >> 16);
}
return new string(result);
}
También probé variantes de esto usando ushort
, struct{char X1, X2}
, struct{byte X1, X2}
en la tabla de búsqueda.
Dependiendo del objetivo de compilación (x86, X64), estos tuvieron el mismo rendimiento o fueron ligeramente más lentos que esta variante.
Y para un rendimiento aún mayor, su hermano unsafe
:
private static readonly uint[] _lookup32Unsafe = CreateLookup32Unsafe();
private static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();
private static uint[] CreateLookup32Unsafe()
{
var result = new uint[256];
for (int i = 0; i < 256; i++)
{
string s=i.ToString("X2");
if(BitConverter.IsLittleEndian)
result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
else
result[i] = ((uint)s[1]) + ((uint)s[0] << 16);
}
return result;
}
public static string ByteArrayToHexViaLookup32Unsafe(byte[] bytes)
{
var lookupP = _lookup32UnsafeP;
var result = new char[bytes.Length * 2];
fixed(byte* bytesP = bytes)
fixed (char* resultP = result)
{
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++)
{
resultP2[i] = lookupP[bytesP[i]];
}
}
return new string(result);
}
O si consideras aceptable escribir directamente en la cadena:
public static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes)
{
var lookupP = _lookup32UnsafeP;
var result = new string((char)0, bytes.Length * 2);
fixed (byte* bytesP = bytes)
fixed (char* resultP = result)
{
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++)
{
resultP2[i] = lookupP[bytesP[i]];
}
}
return result;
}
Puede utilizar el método BitConverter.ToString:
byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256}
Console.WriteLine( BitConverter.ToString(bytes));
Salida:
00-01-02-04-08-10-20-40-80-FF
Más información: BitConverter.ToString Method (Byte [])
Si desea más flexibilidad que BitConverter
, pero no desea esos bucles explícitos de estilo de los años 90, puede hacer:
String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));
O, si estás utilizando .NET 4.0:
String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));
(Este último de un comentario en la publicación original.)
Ya sea:
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
o:
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
Hay incluso más variantes de hacerlo, por ejemplo here .
La conversión inversa sería así:
public static byte[] StringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
Usar la Substring
es la mejor opción en combinación con Convert.ToByte
. Vea esta respuesta para más información. Si necesita un mejor rendimiento, debe evitar Convert.ToByte
antes de poder eliminar SubString
.
Métodos de extensión (descargo de responsabilidad: código completamente no probado, BTW ...):
public static class ByteExtensions
{
public static string ToHexString(this byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
{
hex.AppendFormat("{0:x2}", b);
}
return hex.ToString();
}
}
etc. Utilice cualquiera de las tres soluciones de Tomalak (la última es un método de extensión en una cadena).
Versiones seguras:
public static class HexHelper
{
[System.Diagnostics.Contracts.Pure]
public static string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string hexAlphabet = @"0123456789ABCDEF";
var chars = new char[checked(value.Length * 2)];
unchecked
{
for (int i = 0; i < value.Length; i++)
{
chars[i * 2] = hexAlphabet[value[i] >> 4];
chars[i * 2 + 1] = hexAlphabet[value[i] & 0xF];
}
}
return new string(chars);
}
[System.Diagnostics.Contracts.Pure]
public static byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = value[i * 2]; // High 4 bits.
int val = ((b - ''0'') + (((''9'' - b) >> 31) & -7)) << 4;
b = value[i * 2 + 1]; // Low 4 bits.
val += (b - ''0'') + (((''9'' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
return result;
}
}
}
Versiones inseguras Para quienes prefieren el rendimiento y no temen la inseguridad. Cerca de un 35% más rápido de ToHex y un 10% más rápido de FromHex.
public static class HexUnsafeHelper
{
[System.Diagnostics.Contracts.Pure]
public static unsafe string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string alphabet = @"0123456789ABCDEF";
string result = new string('' '', checked(value.Length * 2));
fixed (char* alphabetPtr = alphabet)
fixed (char* resultPtr = result)
{
char* ptr = resultPtr;
unchecked
{
for (int i = 0; i < value.Length; i++)
{
*ptr++ = *(alphabetPtr + (value[i] >> 4));
*ptr++ = *(alphabetPtr + (value[i] & 0xF));
}
}
}
return result;
}
[System.Diagnostics.Contracts.Pure]
public static unsafe byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
fixed (char* valuePtr = value)
{
char* valPtr = valuePtr;
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = *valPtr++; // High 4 bits.
int val = ((b - ''0'') + (((''9'' - b) >> 31) & -7)) << 4;
b = *valPtr++; // Low 4 bits.
val += (b - ''0'') + (((''9'' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
}
return result;
}
}
}
Por cierto, para la prueba de referencia, inicializar el alfabeto cada vez que la función de conversión llamada sea incorrecta, el alfabeto debe ser constante (para cadena) o lectura estática (para char []). Luego, la conversión de byte [] a cadena basada en el alfabeto se convierte en tan rápida como las versiones de manipulación de bytes.
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 (igual que en "Habilitar solo mi código" si el código debe ser depurable).
Complemento a responder por @CodesInChaos (método invertido)
public static byte[] HexToByteUsingByteManipulation(string s)
{
byte[] bytes = new byte[s.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
int hi = s[i*2] - 65;
hi = hi + 10 + ((hi >> 31) & 7);
int lo = s[i*2 + 1] - 65;
lo = lo + 10 + ((lo >> 31) & 7) & 0x0f;
bytes[i] = (byte) (lo | hi << 4);
}
return bytes;
}
Explicación:
& 0x0f
Es para 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 el mismo 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 0
usando & 0x0f
.
65 es el código para ''A''
48 es el código para ''0''
7 es el número de letras entre ''9''
y ''A''
en la tabla ASCII ( ...456789:;<=>?@ABCD...
).
De los desarrolladores de Microsoft, una conversión agradable y simple:
public static string ByteArrayToString(byte[] ba)
{
// Concatenate the bytes into one long string
return ba.Aggregate(new StringBuilder(32),
(sb, b) => sb.Append(b.ToString("X2"))
).ToString();
}
Mientras que lo anterior es limpio y compacto, los adictos al rendimiento gritarán al respecto utilizando enumeradores. Puede obtener el máximo rendimiento con una versión mejorada de la respuesta original de Tomolak:
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
for(int i=0; i < ga.Length; i++) // <-- Use for loop is faster than foreach
hex.Append(ba[i].ToString("X2")); // <-- ToString is faster than AppendFormat
return hex.ToString();
}
Esta es la más rápida de todas las rutinas que he visto publicadas aquí hasta ahora. No solo tome mi palabra para eso ... pruebe el rendimiento de cada rutina e inspeccione su código CIL.
Dos mashups que doblan las dos operaciones de nibble en una.
Probablemente la versión bastante eficiente:
public static string ByteArrayToString2(byte[] ba)
{
char[] c = new char[ba.Length * 2];
for( int i = 0; i < ba.Length * 2; ++i)
{
byte b = (byte)((ba[i>>1] >> 4*((i&1)^1)) & 0xF);
c[i] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string( c );
}
Decadente versión linq-with-bit-hacking:
public static string ByteArrayToString(byte[] ba)
{
return string.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) );
}
Y revertir:
public static byte[] HexStringToByteArray( string s )
{
byte[] ab = new byte[s.Length>>1];
for( int i = 0; i < s.Length; i++ )
{
int b = s[i];
b = (b - ''0'') + (((''9'' - b)>>31)&-7);
ab[i>>1] |= (byte)(b << 4*((i&1)^1));
}
return ab;
}
En términos de velocidad, esto parece ser mejor que nada aquí:
public static string ToHexString(byte[] data) {
byte b;
int i, j, k;
int l = data.Length;
char[] r = new char[l * 2];
for (i = 0, j = 0; i < l; ++i) {
b = data[i];
k = b >> 4;
r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
k = b & 15;
r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
}
return new string(r);
}
Esta versión de ByteArrayToHexViaByteManipulation podría ser más rápida.
De mis informes:
- ByteArrayToHexViaByteManipulation3: 1,68 tics promedio (más de 1000 ejecuciones), 17,5x
- ByteArrayToHexViaByteManipulation2: 1,73 tics promedio (más de 1000 ejecuciones), 16,9X
- ByteArrayToHexViaByteManipulation: 2,90 tics promedio (más de 1000 ejecuciones), 10,1X
- ByteArrayToHexViaLookupAndShift: 3,22 tics promedio (más de 1000 ejecuciones), 9,1X
...
static private readonly char[] hexAlphabet = new char[] {''0'',''1'',''2'',''3'',''4'',''5'',''6'',''7'',''8'',''9'',''A'',''B'',''C'',''D'',''E'',''F''}; static string ByteArrayToHexViaByteManipulation3(byte[] bytes) { char[] c = new char[bytes.Length * 2]; byte b; for (int i = 0; i < bytes.Length; i++) { b = ((byte)(bytes[i] >> 4)); c[i * 2] = hexAlphabet[b]; b = ((byte)(bytes[i] & 0xF)); c[i * 2 + 1] = hexAlphabet[b]; } return new string(c); }
Y creo que esta es una optimización:
static private readonly char[] hexAlphabet = new char[]
{''0'',''1'',''2'',''3'',''4'',''5'',''6'',''7'',''8'',''9'',''A'',''B'',''C'',''D'',''E'',''F''};
static string ByteArrayToHexViaByteManipulation4(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
for (int i = 0, ptr = 0; i < bytes.Length; i++, ptr += 2)
{
byte b = bytes[i];
c[ptr] = hexAlphabet[b >> 4];
c[ptr + 1] = hexAlphabet[b & 0xF];
}
return new string(c);
}
Función inversa para el código Waleed Eissa (Hex String To Byte Array):
public static byte[] HexToBytes(this string hexString)
{
byte[] b = new byte[hexString.Length / 2];
char c;
for (int i = 0; i < hexString.Length / 2; i++)
{
c = hexString[i * 2];
b[i] = (byte)((c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57)) << 4);
c = hexString[i * 2 + 1];
b[i] += (byte)(c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57));
}
return b;
}
Función Waleed Eissa con soporte en minúsculas:
public static string BytesToHex(this byte[] barray, bool toLowerCase = true)
{
byte addByte = 0x37;
if (toLowerCase) addByte = 0x57;
char[] c = new char[barray.Length * 2];
byte b;
for (int i = 0; i < barray.Length; ++i)
{
b = ((byte)(barray[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + addByte : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + addByte : b + 0x30);
}
return new string(c);
}
No optimizado para la velocidad, pero más LINQy que la mayoría de las respuestas (.NET 4.0):
<Extension()>
Public Function FromHexToByteArray(hex As String) As Byte()
hex = If(hex, String.Empty)
If hex.Length Mod 2 = 1 Then hex = "0" & hex
Return Enumerable.Range(0, hex.Length / 2).Select(Function(i) Convert.ToByte(hex.Substring(i * 2, 2), 16)).ToArray
End Function
<Extension()>
Public Function ToHexString(bytes As IEnumerable(Of Byte)) As String
Return String.Concat(bytes.Select(Function(b) b.ToString("X2")))
End Function
Otra función rápida ...
private static readonly byte[] HexNibble = new byte[] {
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
};
public static byte[] HexStringToByteArray( string str )
{
int byteCount = str.Length >> 1;
byte[] result = new byte[byteCount + (str.Length & 1)];
for( int i = 0; i < byteCount; i++ )
result[i] = (byte) (HexNibble[str[i << 1] - 48] << 4 | HexNibble[str[(i << 1) + 1] - 48]);
if( (str.Length & 1) != 0 )
result[byteCount] = (byte) HexNibble[str[str.Length - 1] - 48];
return result;
}
Otra variación más para la diversidad:
public static byte[] FromHexString(string src)
{
if (String.IsNullOrEmpty(src))
return null;
int index = src.Length;
int sz = index / 2;
if (sz <= 0)
return null;
byte[] rc = new byte[sz];
while (--sz >= 0)
{
char lo = src[--index];
char hi = src[--index];
rc[sz] = (byte)(
(
(hi >= ''0'' && hi <= ''9'') ? hi - ''0'' :
(hi >= ''a'' && hi <= ''f'') ? hi - ''a'' + 10 :
(hi >= ''A'' && hi <= ''F'') ? hi - ''A'' + 10 :
0
)
<< 4 |
(
(lo >= ''0'' && lo <= ''9'') ? lo - ''0'' :
(lo >= ''a'' && lo <= ''f'') ? lo - ''a'' + 10 :
(lo >= ''A'' && lo <= ''F'') ? lo - ''A'' + 10 :
0
)
);
}
return rc;
}
Y para insertar en una cadena SQL (si no está usando parámetros de comando):
public static String ByteArrayToSQLHexString(byte[] Source)
{
return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
¿Por qué hacerlo complejo? Esto es simple en Visual Studio 2008:
DO#:
string hex = BitConverter.ToString(YourByteArray).Replace("-", "");
VB:
Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Aquí está mi oportunidad. He creado un par de clases de extensión para extender la cadena y el byte. En la prueba de archivos grandes, el rendimiento es comparable a la manipulación de bytes 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 una foreach
iteración y un contador es más rápido que una indexación explícita for
.
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 prueba también son de interés:
ToHexStringCharArrayWithCharArrayLookup: 41,589.69 ticks promedio (más de 1000 ejecuciones), 1,5X ToHexStringCharArrayWithStringLookup: 50,764.06 ticks promedio en el que se encuentra el personal de la empresa.
Sobre la base de los resultados anteriores, parece seguro concluir que:
- Las penalizaciones por la indexación en una cadena para realizar la búsqueda frente a una matriz de caracteres son importantes en la prueba de archivos grandes.
- Las penalizaciones por usar un StringBuilder de capacidad conocida frente a una matriz de caracteres de tamaño conocido para crear la cadena son aún más importantes.
Aquí está el código:
using System;
namespace ConversionExtensions
{
public static class ByteArrayExtensions
{
private readonly static char[] digits = new char[] { ''0'', ''1'', ''2'', ''3'', ''4'', ''5'', ''6'', ''7'', ''8'', ''9'', ''A'', ''B'', ''C'', ''D'', ''E'', ''F'' };
public static string ToHexString(this byte[] bytes)
{
char[] hex = new char[bytes.Length * 2];
int index = 0;
foreach (byte b in bytes)
{
hex[index++] = digits[b >> 4];
hex[index++] = digits[b & 0x0F];
}
return new string(hex);
}
}
}
using System;
using System.IO;
namespace ConversionExtensions
{
public static class StringExtensions
{
public static byte[] ToBytes(this string hexString)
{
if (!string.IsNullOrEmpty(hexString) && hexString.Length % 2 != 0)
{
throw new FormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");
}
hexString = hexString.ToUpperInvariant();
byte[] data = new byte[hexString.Length / 2];
for (int index = 0; index < hexString.Length; index += 2)
{
int highDigitValue = hexString[index] <= ''9'' ? hexString[index] - ''0'' : hexString[index] - ''A'' + 10;
int lowDigitValue = hexString[index + 1] <= ''9'' ? hexString[index + 1] - ''0'' : hexString[index + 1] - ''A'' + 10;
if (highDigitValue < 0 || lowDigitValue < 0 || highDigitValue > 15 || lowDigitValue > 15)
{
throw new FormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");
}
else
{
byte value = (byte)((highDigitValue << 4) | (lowDigitValue & 0x0F));
data[index / 2] = value;
}
}
return data;
}
}
}
A continuación se muestran los resultados de las pruebas que obtuve cuando puse mi código en el proyecto de pruebas de @ patridge en mi máquina. También agregué una prueba para convertir a una matriz de bytes desde hexadecimal. Las pruebas que ejecutaron mi código son ByteArrayToHexViaOptimizedLookupAndShift y HexToByteArrayViaByteManipulation. El HexToByteArrayViaConvertToByte se tomó de XXXX. El HexToByteArrayViaSoapHexBinary es el de la respuesta de @Mykroft.
Procesador Intel Pentium III Xeon
Cores: 4 <br/> Current Clock Speed: 1576 <br/> Max Clock Speed: 3092 <br/>
Convertir una matriz de bytes en una representación de cadena hexadecimal
ByteArrayToHexViaByteManipulation2: 39,366.64 tics promedio (más de 1000 ejecuciones), 22.4X
ByteArrayToHexViaOptimizedLookupAndShift: 41,588.64 tics promedio (más de 1000 ejecuciones), 21.2X
ByteArrayToHexViaLookup: 55,509.56 tics promedio (más de 1000 carreras), 15.9X
ByteArrayToHexViaByteManipulation: 65,349.12 tics promedio (más de 1000 ejecuciones), 13.5X
ByteArrayToHexViaLookupAndShift: 86,926.87 tics promedio (más de 1000 carreras), 10.2X
ByteArrayToHexStringViaBitConverter: 139,353.73 tics promedio (más de 1000 ejecuciones), 6.3X
ByteArrayToHexViaSoapHexBinary: 314,598.77 tics promedio (más de 1000 ejecuciones), 2.8X
ByteArrayToHexStringViaStringBuilderForEachByteToString: 344,264.63 tics promedio (más de 1000 ejecuciones), 2.6X
ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382,623.44 tics promedio (más de 1000 ejecuciones), 2.3X
ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818,111.95 tics promedio (más de 1000 ejecuciones), 1.1X
ByteArrayToHexStringViaStringConcatArrayConvertAll: 839,244.84 tics promedio (más de 1000 ejecuciones), 1.1X
ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867,303.98 tics promedio (más de 1000 ejecuciones), 1.0X
ByteArrayToHexStringViaStringJoinArrayConvertAll: 882,710.28 tics promedio (más de 1000 ejecuciones), 1.0X
Entraré en esta competencia de juguetear un poco ya que tengo una respuesta que también utiliza el violín de bits para decodificar hexadecimales. Tenga en cuenta que el uso de matrices de caracteres puede ser incluso más rápido, ya que los StringBuilder
métodos de llamada también llevarán tiempo.
public static String ToHex (byte[] data)
{
int dataLength = data.Length;
// pre-create the stringbuilder using the length of the data * 2, precisely enough
StringBuilder sb = new StringBuilder (dataLength * 2);
for (int i = 0; i < dataLength; i++) {
int b = data [i];
// check using calculation over bits to see if first tuple is a letter
// isLetter is zero if it is a digit, 1 if it is a letter
int isLetter = (b >> 7) & ((b >> 6) | (b >> 5)) & 1;
// calculate the code using a multiplication to make up the difference between
// a digit character and an alphanumerical character
int code = ''0'' + ((b >> 4) & 0xF) + isLetter * (''A'' - ''9'' - 1);
// now append the result, after casting the code point to a character
sb.Append ((Char)code);
// do the same with the lower (less significant) tuple
isLetter = (b >> 3) & ((b >> 2) | (b >> 1)) & 1;
code = ''0'' + (b & 0xF) + isLetter * (''A'' - ''9'' - 1);
sb.Append ((Char)code);
}
return sb.ToString ();
}
public static byte[] FromHex (String hex)
{
// pre-create the array
int resultLength = hex.Length / 2;
byte[] result = new byte[resultLength];
// set validity = 0 (0 = valid, anything else is not valid)
int validity = 0;
int c, isLetter, value, validDigitStruct, validDigit, validLetterStruct, validLetter;
for (int i = 0, hexOffset = 0; i < resultLength; i++, hexOffset += 2) {
c = hex [hexOffset];
// check using calculation over bits to see if first char is a letter
// isLetter is zero if it is a digit, 1 if it is a letter (upper & lowercase)
isLetter = (c >> 6) & 1;
// calculate the tuple value using a multiplication to make up the difference between
// a digit character and an alphanumerical character
// minus 1 for the fact that the letters are not zero based
value = ((c & 0xF) + isLetter * (-1 + 10)) << 4;
// check validity of all the other bits
validity |= c >> 7; // changed to >>, maybe not OK, use UInt?
validDigitStruct = (c & 0x30) ^ 0x30;
validDigit = ((c & 0x8) >> 3) * (c & 0x6);
validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);
validLetterStruct = c & 0x18;
validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
validity |= isLetter * (validLetterStruct | validLetter);
// do the same with the lower (less significant) tuple
c = hex [hexOffset + 1];
isLetter = (c >> 6) & 1;
value ^= (c & 0xF) + isLetter * (-1 + 10);
result [i] = (byte)value;
// check validity of all the other bits
validity |= c >> 7; // changed to >>, maybe not OK, use UInt?
validDigitStruct = (c & 0x30) ^ 0x30;
validDigit = ((c & 0x8) >> 3) * (c & 0x6);
validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);
validLetterStruct = c & 0x18;
validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
validity |= isLetter * (validLetterStruct | validLetter);
}
if (validity != 0) {
throw new ArgumentException ("Hexadecimal encoding incorrect for input " + hex);
}
return result;
}
Convertido del código de Java.
Este es un gran post 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 la 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 Patidge.
private byte[] HexStringToByteArray(string hexString)
{
int hexStringLength = hexString.Length;
byte[] b = new byte[hexStringLength / 2];
for (int i = 0; i < hexStringLength; i += 2)
{
int topChar = (hexString[i] > 0x40 ? hexString[i] - 0x37 : hexString[i] - 0x30) << 4;
int bottomChar = hexString[i + 1] > 0x40 ? hexString[i + 1] - 0x37 : hexString[i + 1] - 0x30;
b[i / 2] = Convert.ToByte(topChar + bottomChar);
}
return b;
}
No es suficiente con las muchas respuestas aquí, pero encontré una implementación bastante óptima (aproximadamente 4.5 veces mejor que la aceptada) del analizador de cadena hexadecimal. Primero, el resultado de mis pruebas (el primer lote es mi implementación):
Give me that string:
04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939f
Time to parse 100,000 times: 50.4192 ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter''d: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Accepted answer: (StringToByteArray)
Time to parse 100000 times: 233.1264ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter''d: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With Mono''s implementation:
Time to parse 100000 times: 777.2544ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter''d: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With SoapHexBinary:
Time to parse 100000 times: 845.1456ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter''d: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Las líneas base64 y ''BitConverter''d'' están ahí para probar su corrección. Tenga en cuenta que son iguales.
La implementación:
public static byte[] ToByteArrayFromHex(string hexString)
{
if (hexString.Length % 2 != 0) throw new ArgumentException("String must have an even length");
var array = new byte[hexString.Length / 2];
for (int i = 0; i < hexString.Length; i += 2)
{
array[i/2] = ByteFromTwoChars(hexString[i], hexString[i + 1]);
}
return array;
}
private static byte ByteFromTwoChars(char p, char p_2)
{
byte ret;
if (p <= ''9'' && p >= ''0'')
{
ret = (byte) ((p - ''0'') << 4);
}
else if (p <= ''f'' && p >= ''a'')
{
ret = (byte) ((p - ''a'' + 10) << 4);
}
else if (p <= ''F'' && p >= ''A'')
{
ret = (byte) ((p - ''A'' + 10) << 4);
} else throw new ArgumentException("Char is not a hex digit: " + p,"p");
if (p_2 <= ''9'' && p_2 >= ''0'')
{
ret |= (byte) ((p_2 - ''0''));
}
else if (p_2 <= ''f'' && p_2 >= ''a'')
{
ret |= (byte) ((p_2 - ''a'' + 10));
}
else if (p_2 <= ''F'' && p_2 >= ''A'')
{
ret |= (byte) ((p_2 - ''A'' + 10));
} else throw new ArgumentException("Char is not a hex digit: " + p_2, "p_2");
return ret;
}
Intenté algunas cosas con unsafe
y moviendo la if
secuencia (claramente redundante) de carácter a mordisco a otro método, pero esto fue lo más rápido que obtuvo.
(Admito que esto responde a la mitad de la pregunta. Sentí que la conversión string-> byte [] estaba subrepresentada, mientras que el byte [] -> ángulo de string parece estar bien cubierto. Por lo tanto, esta respuesta).
No obtuve el código que sugieres para trabajar, Olipro. hex[i] + hex[i+1]
aparentemente devolvió un int
.
Sin embargo, tuve cierto éxito al tomar algunos consejos del código de Waleeds y armar esto juntos. Es feo como el infierno, pero parece funcionar y funciona en 1/3 del tiempo en comparación con los demás según mis pruebas (utilizando el mecanismo de prueba de patridges). Dependiendo del tamaño de entrada. Cambiar alrededor de?: S para separar primero 0-9 probablemente arrojaría un resultado un poco más rápido ya que hay más números que letras.
public static byte[] StringToByteArray2(string hex)
{
byte[] bytes = new byte[hex.Length/2];
int bl = bytes.Length;
for (int i = 0; i < bl; ++i)
{
bytes[i] = (byte)((hex[2 * i] > ''F'' ? hex[2 * i] - 0x57 : hex[2 * i] > ''9'' ? hex[2 * i] - 0x37 : hex[2 * i] - 0x30) << 4);
bytes[i] |= (byte)(hex[2 * i + 1] > ''F'' ? hex[2 * i + 1] - 0x57 : hex[2 * i + 1] > ''9'' ? hex[2 * i + 1] - 0x37 : hex[2 * i + 1] - 0x30);
}
return bytes;
}
Otra forma es usando stackalloc
para reducir la presión de la memoria del GC:
static string ByteToHexBitFiddle(byte[] bytes)
{
var c = stackalloc char[bytes.Length * 2 + 1];
int b;
for (int i = 0; i < bytes.Length; ++i)
{
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
}
c[bytes.Length * 2 ] = ''/0'';
return new string(c);
}
Para el rendimiento me gustaría ir con la solución drphrozens. Una pequeña optimización para el decodificador podría ser usar una tabla para que cualquiera de los dos personajes se deshaga de "<< 4".
Claramente las dos llamadas de método son costosas. Si se realiza algún tipo de comprobación en los datos de entrada o salida (podría ser CRC, suma de comprobación o lo que sea), se if (b == 255)...
podría omitir y, por lo tanto, también el método se llama por completo.
Usar offset++
y en offset
lugar de offset
y offset + 1
podría dar algún beneficio teórico, pero sospecho que el compilador maneja esto mejor que yo.
private static readonly byte[] LookupTableLow = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static readonly byte[] LookupTableHigh = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static byte LookupLow(char c)
{
var b = LookupTableLow[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
private static byte LookupHigh(char c)
{
var b = LookupTableHigh[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
public static byte ToByte(char[] chars, int offset)
{
return (byte)(LookupHigh(chars[offset++]) | LookupLow(chars[offset]));
}
Esto está justo en la parte superior de mi cabeza y no se ha probado ni evaluado.