¿Cómo se puede convertir una matriz de bytes en una cadena hexadecimal y viceversa?
¿Cómo se puede convertir una matriz de bytes en una cadena hexadecimal y viceversa?
Respuestas:
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 aquí .
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 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 evitarlo Convert.ToByte
antes de poder caer SubString
.
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 la StringBuilder
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.
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á.
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")
(usando foreach
) (derivado de la respuesta de Will Dean)
{byte}.ToString("X2")
(usando {IEnumerable}.Aggregate
, requiere System.Linq) (a través de Mark)
Array.ConvertAll
(usando string.Join
) (a través de Will Dean)
Array.ConvertAll
(usando string.Concat
, requiere .NET 4.0) (a través de Will Dean)
{StringBuilder}.AppendFormat
(usando foreach
) (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.
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.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();
}
Se agregó la respuesta de Waleed al análisis. Bastante rapido.
string.Concat
Array.ConvertAll
Variante agregada para completar (requiere .NET 4.0). A la par destring.Join
versión.
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, pero BitConverter
aún así gana.
Se agregó la SoapHexBinary
respuesta de Mykroft al análisis, que tomó el tercer lugar.
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).
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).
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.
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.
bytes.ToHexStringAtLudicrousSpeed()
).
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();
}
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.
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 entres aquí
Una explicación del poco extraño violín:
bytes[i] >> 4
extrae el mordisco alto de un byte bytes[i] & 0xF
extrae el mordisco bajo de un byteb - 10
< 0
para los valores b < 10
, que se convertirá en un dígito decimal >= 0
para los valores b > 10
, que se convertirán en una carta A
a F
.i >> 31
de un entero de 32 bits con signo extrae el signo, gracias a la extensión de signo. Será -1
por i < 0
y 0
para i >= 0
.(b-10)>>31
será 0
para letras y -1
para dígitos.0
, y b
está en el rango de 10 a 15. Queremos asignarlo a A
(65) a F
(70), lo que implica sumar 55 ( 'A'-10
).b
del rango de 0 a 9 al rango 0
(48) a 9
(57). Esto significa que necesita convertirse en -7 ( '0' - 55
). & -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ímites bytes[i]
, por lo que elegí esa variante.b
un int permite conversiones innecesarias desde y hacia byte.hex string
para byte[] array
?
87 + b + (((b-10)>>31)&-39)
byte[] array
", que literalmente significa un conjunto de conjuntos de bytes, o byte[][]
. 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:
String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));
O, si está utilizando .NET 4.0:
String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));
(Esto último de un comentario en la publicación original).
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.
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 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:
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 considera 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;
}
Span
se puede usar ahora en lugar de unsafe
??
Puede usar 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 [])
Acabo de encontrar el mismo problema hoy, y me encontré con 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 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).
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
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 (vea otras respuestas) que no se usa Convert.ToByte
si necesita rendimiento. No , no utilizar ninguna otra cosa que no sea String.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 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:
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 utiliza un StringReader
en 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.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.
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 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.
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 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 llamar String.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.
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 necesita un j
índice que incremente en pasos de dos paralelos a i
? Por supuesto que no, simplemente multiplique i
por dos (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 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, pero String.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 utilizado CopyTo
directamente allí para evitar la llamada sobrecarga.
String.Substring
Método manual
¿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.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.
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.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
¡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).
Complemento para responder por @CodesInChaos (método inverso)
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 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 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...
).
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.
private static readonly byte[] LookupTable = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static byte Lookup(char c)
{
var b = LookupTable[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
public static byte ToByte(char[] chars, int offset)
{
return (byte)(Lookup(chars[offset]) << 4 | Lookup(chars[offset + 1]));
}
private static readonly char[][] LookupTableUpper;
private static readonly char[][] LookupTableLower;
static Hex()
{
LookupTableLower = new char[256][];
LookupTableUpper = new char[256][];
for (var i = 0; i < 256; i++)
{
LookupTableLower[i] = i.ToString("x2").ToCharArray();
LookupTableUpper[i] = i.ToString("X2").ToCharArray();
}
}
public static char[] ToCharLower(byte[] b, int bOffset)
{
return LookupTableLower[b[bOffset]];
}
public static char[] ToCharUpper(byte[] b, int bOffset)
{
return LookupTableUpper[b[bOffset]];
}
StringBuilderToStringFromBytes: 106148
BitConverterToStringFromBytes: 15783
ArrayConvertAllToStringFromBytes: 54290
ByteManipulationToCharArray: 8444
TableBasedToCharArray: 5651 *
* esta solución
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.
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.
private byte[] HexStringToByteArray(string hexString)
{
int hexStringLength = hexString.Length;
byte[] b = new byte[hexStringLength / 2];
for (int i = 0; i < hexStringLength; i += 2)
{
int topChar = (hexString[i] > 0x40 ? hexString[i] - 0x37 : hexString[i] - 0x30) << 4;
int bottomChar = hexString[i + 1] > 0x40 ? hexString[i + 1] - 0x37 : hexString[i + 1] - 0x30;
b[i / 2] = Convert.ToByte(topChar + bottomChar);
}
return b;
}
hexString[i] &= ~0x20;
¿Por qué hacerlo complejo? Esto es simple en Visual Studio 2008:
C#:
string hex = BitConverter.ToString(YourByteArray).Replace("-", "");
VB:
Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
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:
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 la 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 unsafe
y moví la if
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).
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 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.
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 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):
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 de 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);
}
Métodos de extensión (descargo de responsabilidad: código completamente no probado, por cierto ...):
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 (siendo la última un método de extensión en una cadena).
De los desarrolladores de Microsoft, una conversión simple y agradable:
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();
}
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 :
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
for(int i=0; i < ba.Length; i++) // <-- Use for loop is faster than foreach
hex.Append(ba[i].ToString("X2")); // <-- ToString is faster than AppendFormat
return hex.ToString();
}
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.
b.ToSting("X2")
.
Y para insertar en una cadena SQL (si no está utilizando parámetros de comando):
public static String ByteArrayToSQLHexString(byte[] Source)
{
return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
Source == null
o Source.Length == 0
tenemos un problema señor!
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);
}
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.
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;
}
Esta versión de ByteArrayToHexViaByteManipulation podría ser más rápida.
De mis informes:
...
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);
}
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.
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 de código Java.
Char[]
y usarlo Char
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 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 ha sido probado ni comparado.
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;
}
No está optimizado para la velocidad, pero tiene 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
Dos mashups que pliegan las dos operaciones de mordisco en una.
Probablemente 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 );
}
Versión decadente de 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 al revés:
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;
}
Otra forma es usar 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);
}
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:
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 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.
Procesador Intel Pentium III Xeon
Cores: 4 <br/> Current Clock Speed: 1576 <br/> Max Clock Speed: 3092 <br/>
Convertir matriz de bytes en representación de cadena hexadecimal
ByteArrayToHexViaByteManipulation2: 39,366.64 ticks promedio (más de 1000 carreras), 22.4X
ByteArrayToHexViaOptimizedLookupAndShift: 41,588.64 ticks promedio (más de 1000 carreras), 21.2X
ByteArrayToHexViaLookup: 55,509.56 ticks promedio (más de 1000 carreras), 15.9X
ByteArrayToHexViaByteManipulation: 65,349.12 ticks promedio (más de 1000 carreras), 13.5X
ByteArrayToHexViaLookupAndShift: 86,926.87 ticks promedio (más de 1000 carreras), 10.2X
ByteArrayToHexStringViaBitConverter: 139,353.73 ticks promedio (más de 1000 carreras), 6.3X
ByteArrayToHexViaSoapHexBinary: 314,598.77 ticks promedio (más de 1000 carreras), 2.8X
ByteArrayToHexStringViaStringBuilderForEachByteToString: 344,264.63 ticks promedio (más de 1000 carreras), 2.6X
ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382,623.44 ticks promedio (más de 1000 carreras), 2.3X
ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818,111.95 ticks promedio (más de 1000 carreras), 1.1X
ByteArrayToHexStringViaStringConcatArrayConvertAll: 839,244.84 ticks promedio (más de 1000 carreras), 1.1X
ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867,303.98 ticks promedio (más de 1000 carreras), 1.0X
ByteArrayToHexStringViaStringJoinArrayConvertAll: 882,710.28 ticks promedio (más de 1000 carreras), 1.0X
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;
}