¿Cómo se convierte una matriz de bytes en una cadena hexadecimal y viceversa?

1372

¿Cómo se puede convertir una matriz de bytes en una cadena hexadecimal y viceversa?

alextansc
fuente
8
La respuesta aceptada a continuación parece asignar una horrible cantidad de cadenas en la conversión de cadenas a bytes. Me pregunto cómo esto afecta el rendimiento
Wim Coenen
99
La clase SoapHexBinary hace exactamente lo que quieres, creo.
Mykroft
Me parece que hacer 2 preguntas en 1 publicación no es del todo estándar.
SandRock

Respuestas:

1353

Ya sea:

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 Substringes la mejor opción en combinación con Convert.ToByte. Vea esta respuesta para más información. Si necesita un mejor rendimiento, debe evitarlo Convert.ToByteantes de poder caer SubString.

Tomalak
fuente
24
Estás usando SubString. ¿Este bucle no asigna una cantidad horrible de objetos de cadena?
Wim Coenen
30
Honestamente, hasta que se reduzca drásticamente el rendimiento, tendería a ignorar esto y confiar en el Runtime y el GC para encargarse de ello.
Tomalak
87
Debido a que un byte es dos nibbles, cualquier cadena hexadecimal que represente válidamente una matriz de bytes debe tener un recuento de caracteres par. No se debe agregar un 0 en ninguna parte: agregar uno supondría que los datos no válidos son potencialmente peligrosos. En todo caso, el método StringToByteArray debería lanzar una FormatException si la cadena hexadecimal contiene un número impar de caracteres.
David Boike
77
@ 00jt Debe suponer que F == 0F. O es lo mismo que 0F, o la entrada se recortó y F es en realidad el comienzo de algo que no ha recibido. Depende de su contexto hacer esas suposiciones, pero creo que una función de propósito general debería rechazar los caracteres impares como inválidos en lugar de hacer esa suposición para el código de llamada.
David Boike
11
@DavidBoike La pregunta no tenía NADA que ver con "cómo manejar valores de flujo posiblemente recortados" Se trata de una cadena. String myValue = 10.ToString ("X"); myValue es "A", no "0A". Ahora ve a leer esa cadena de nuevo a bytes, oops, la rompiste.
00jt
488

Análisis de rendimiento

Nota: nuevo líder a partir del 2015-08-20.

Ejecuté cada uno de los diversos métodos de conversión a través de algunas Stopwatchpruebas de rendimiento en bruto , una ejecución con una oración aleatoria (n = 61, 1000 iteraciones) y una ejecución con un texto del Proyecto Gutenburg (n = 1,238,957, 150 iteraciones). Aquí están los resultados, aproximadamente del más rápido al más lento. Todas las mediciones están en ticks ( 10,000 ticks = 1 ms ) y todas las notas relativas se comparan con la StringBuilderimplementación [más lenta] . Para el código utilizado, vea a continuación o el repositorio de marco de prueba donde ahora mantengo el código para ejecutar esto.

Descargo de responsabilidad

ADVERTENCIA: No confíe en estas estadísticas para nada concreto; son simplemente una muestra de datos de muestra. Si realmente necesita un rendimiento de primer nivel, pruebe estos métodos en un entorno representativo de sus necesidades de producción con datos representativos de lo que utilizará.

Resultados

Las tablas de búsqueda han tomado la delantera sobre la manipulación de bytes. Básicamente, hay alguna forma de precomputar lo que cualquier mordisco o byte dado estará en hexadecimal. Luego, mientras revisa los datos, simplemente busca la siguiente porción para ver qué cadena hexadecimal sería. Ese valor se agrega a la salida de cadena resultante de alguna manera. Durante mucho tiempo, la manipulación de bytes, potencialmente más difícil de leer por algunos desarrolladores, fue el enfoque de mayor rendimiento.

Su mejor opción será encontrar algunos datos representativos y probarlos en un entorno similar a la producción. Si tiene diferentes restricciones de memoria, puede preferir un método con menos asignaciones a uno que sea más rápido pero consuma más memoria.

Código de prueba

Siéntase libre de jugar con el código de prueba que utilicé. Aquí se incluye una versión, pero puede clonar el repositorio y agregar sus propios métodos. Envíe una solicitud de extracción si encuentra algo interesante o desea ayudar a mejorar el marco de prueba que utiliza.

  1. Agregue el nuevo método estático ( Func<byte[], string>) a /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Agregue el nombre de ese método al TestCandidatesvalor de retorno en esa misma clase.
  3. Asegúrese de estar ejecutando la versión de entrada que desea, oración o texto, alternando los comentarios GenerateTestInputen esa misma clase.
  4. Presione F5y 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)

string.Concat Array.ConvertAllVariante agregada para completar (requiere .NET 4.0). A la par destring.Join versión.

Actualización (2012-02-05)

El repositorio de prueba incluye más variantes como StringBuilder.Append(b.ToString("X2")). Ninguno alteró los resultados. foreaches más rápido que {IEnumerable}.Aggregate, por ejemplo, pero BitConverteraún así gana.

Actualización (2012-04-03)

Se agregó la SoapHexBinaryrespuesta de Mykroft al análisis, que tomó el tercer lugar.

Actualización (2013-01-15)

Se agregó la respuesta de manipulación de bytes de CodesInChaos, que ocupó el primer lugar (por un gran margen en grandes bloques de texto).

Actualización (2013-05-23)

Se agregó la respuesta de búsqueda de Nathan Moinvaziri y la variante del blog de Brian Lambert. Ambos bastante rápido, pero sin tomar la iniciativa en la máquina de prueba que utilicé (AMD Phenom 9750).

Actualización (2014-07-31)

Se agregó la nueva respuesta de búsqueda basada en bytes de @ CodesInChaos. Parece haber tomado la delantera tanto en las pruebas de oraciones como en las pruebas de texto completo.

Actualización (2015-08-20)

Se agregaron optimizaciones y unsafevariantes de airbreather al repositorio de esta respuesta . Si quieres jugar en el juego inseguro, puedes obtener grandes ganancias de rendimiento sobre cualquiera de los ganadores principales anteriores tanto en cadenas cortas como en textos grandes.

patridge
fuente
¿Te gustaría probar el código de la respuesta de Waleed? Parece ser muy rápido. stackoverflow.com/questions/311165/…
Cristian Diaconescu
55
A pesar de hacer que el código esté disponible para que usted haga lo que solicitó por su cuenta, actualicé el código de prueba para incluir la respuesta de Waleed. Dejando a un lado todo el mal humor, es mucho más rápido.
patridge
2
@CodesInChaos Hecho. Y también ganó bastante en mis pruebas. Todavía no pretendo comprender completamente ninguno de los métodos principales, pero se ocultan fácilmente de la interacción directa.
patridge
66
Esta respuesta no tiene intención de responder a la pregunta de qué es "natural" o común. El objetivo es dar a las personas algunos puntos de referencia básicos de rendimiento ya que, cuando necesita hacer estas conversiones, tiende a hacerlas mucho. Si alguien necesita velocidad bruta, simplemente ejecuta los puntos de referencia con algunos datos de prueba apropiados en su entorno informático deseado. Luego, guarde ese método en un método de extensión donde nunca vuelva a mirar su implementación (por ejemplo, bytes.ToHexStringAtLudicrousSpeed()).
patridge
2
Acabo de producir una implementación basada en una tabla de búsqueda de alto rendimiento. Su variante segura es aproximadamente un 30% más rápida que el líder actual en mi CPU. Las variantes inseguras son aún más rápidas. stackoverflow.com/a/24343727/445517
CodesInChaos
244

Hay una clase llamada SoapHexBinary que hace exactamente lo que quieres.

using System.Runtime.Remoting.Metadata.W3cXsd2001;

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();
}
Mykroft
fuente
35
SoapHexBinary está disponible desde .NET 1.0 y está en mscorlib. A pesar de que es un espacio de nombres divertido, hace exactamente lo que hizo la pregunta.
Sly Gryphon
44
Gran descubrimiento! Tenga en cuenta que deberá rellenar cadenas impares con un 0 inicial para GetStringToBytes, como la otra solución.
Carter Medlin
¿Has visto el pensamiento de implementación? La respuesta aceptada tiene una mejor en mi humilde opinión.
mfloryan
66
Interesante ver la implementación de Mono aquí: github.com/mono/mono/blob/master/mcs/class/corlib/…
Jeremy
1
SoapHexBinary no es compatible con .NET Core / .NET Standard ...
juFo
141

Al escribir código criptográfico, es común evitar ramas dependientes de datos y búsquedas de tablas para garantizar que el tiempo de ejecución no dependa de los datos, ya que el tiempo dependiente de datos puede conducir a ataques de canal lateral.

También es bastante rápido.

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:

  1. bytes[i] >> 4extrae el mordisco alto de un byte
    bytes[i] & 0xFextrae el mordisco bajo de un byte
  2. b - 10
    es < 0para los valores b < 10, que se convertirá en un dígito decimal
    es >= 0para los valores b > 10, que se convertirán en una carta Aa F.
  3. El uso i >> 31de un entero de 32 bits con signo extrae el signo, gracias a la extensión de signo. Será -1por i < 0y 0para i >= 0.
  4. Combinando 2) y 3), muestra que (b-10)>>31será 0para letras y -1para dígitos.
  5. Mirando las mayúsculas y minúsculas para las letras, el último sumando se convierte en 0, y bestá en el rango de 10 a 15. Queremos asignarlo a A(65) a F(70), lo que implica sumar 55 ( 'A'-10).
  6. Mirando el caso de los dígitos, queremos adaptar el último sumando para que se asigne bdel rango de 0 a 9 al rango 0(48) a 9(57). Esto significa que necesita convertirse en -7 ( '0' - 55).
    Ahora podríamos simplemente multiplicar por 7. Pero dado que -1 está representado por todos los bits que son 1, podemos usar & -7desde entonces (0 & -7) == 0y (-1 & -7) == -7.

Algunas consideraciones adicionales:

  • No utilicé una segunda variable de bucle para indexar c, ya que la medición muestra que calcularlo desdei es más barato.
  • Usar exactamente i < bytes.Lengthcomo límite superior del bucle permite que el JITter elimine las comprobaciones de límites bytes[i], por lo que elegí esa variante.
  • Hacer bun int permite conversiones innecesarias desde y hacia byte.
CodesInChaos
fuente
10
Y hex stringpara byte[] array?
AaA
15
+1 por citar correctamente tu fuente después de invocar ese poco de magia negra. Todos saluden a Cthulhu.
Edward
44
¿Qué pasa con la cadena al byte []?
Syaiful Nizam Yahya
99
¡Agradable! Para aquellos que necesitan salida en minúsculas, la expresión obviamente cambia a87 + b + (((b-10)>>31)&-39)
eXavier
2
@AaA Dijiste " byte[] array", que literalmente significa un conjunto de conjuntos de bytes, o byte[][]. Solo me estaba burlando.
CoolOppo
97

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).

Will Dean
fuente
21
Aún más corto: String.Concat (Array.ConvertAll (bytes, x => x.ToString ("X2"))
Nestor el
14
Aún más corto: String.Concat (bytes.Select (b => b.ToString ("X2"))) [.NET4]
Allon Guralnek
14
Solo responde la mitad de la pregunta.
Sly Gryphon
1
¿Por qué el segundo necesita .Net 4? String.Concat está en .Net 2.0.
Polyfun
2
esos bucles de "estilo de los 90" son generalmente más rápidos, pero en una cantidad lo suficientemente insignificante que no importará en la mayoría de los contextos. Todavía vale la pena mencionarlo
Austin_Anderson
69

Otro enfoque basado en la tabla de búsqueda. Éste usa solo una tabla de búsqueda para cada byte, en lugar de una tabla de búsqueda por mordisco.

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 unsafehermano:

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;
}
revs CodesInChaos
fuente
¿Por qué la creación de la tabla de búsqueda en la versión insegura intercambia los nibbles del byte precalculado? Pensé que la endianidad solo cambió el orden de las entidades que se formaron de múltiples bytes.
Raif Atef
@RaifAtef Lo que importa aquí no es el orden de los mordiscos. Pero el orden de las palabras de 16 bits en un entero de 32 bits. Pero estoy considerando reescribirlo para que se pueda ejecutar el mismo código independientemente de la endianidad.
CodesInChaos
Al releer el código, creo que hiciste esto porque cuando lanzas el char * más tarde a un uint * y lo asignas (al generar el char hexadecimal), el tiempo de ejecución / CPU volteará los bytes (ya que uint no se trata igual que 2 caracteres separados de 16 bits), por lo que los voltea previamente para compensar. Estoy en lo cierto? Endianness es confuso :-).
Raif Atef
44
Esto solo responde a la mitad de la pregunta ... ¿Qué tal una cadena hexadecimal a bytes?
Narvalex
3
@CodesInChaos Me pregunto si Spanse puede usar ahora en lugar de unsafe??
Konrad
64

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 [])

Baget
fuente
14
Solo responde la mitad de la pregunta.
Sly Gryphon
3
¿Dónde está la segunda parte de la respuesta?
Sawan
56

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).

Waleed Eissa
fuente
sin mencionar que esto usa menos memoria. No hay cadenas intermedias creadas en absoluto.
Chochos
8
Solo responde la mitad de la pregunta.
Sly Gryphon
Esto es genial porque funciona básicamente en cualquier versión de NET, incluido NETMF. ¡Un ganador!
Jonesome Reinstate a Monica
1
La respuesta aceptada proporciona 2 excelentes métodos HexToByteArray, que representan la otra mitad de la pregunta. La solución de Waleed responde a la pregunta de cómo hacer esto sin crear una gran cantidad de cadenas en el proceso.
Brendten Eickstaedt
¿La nueva cadena (c) copia y reasigna o es lo suficientemente inteligente como para saber cuándo puede simplemente envolver el char []?
jjxtra
19

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.ToBytey String.Substringsi tiene prisa ("Código original" a continuación), es la mejor combinación si no desea volver a implementar Convert.ToByte. Use algo más avanzado (vea otras respuestas) que no se usa Convert.ToBytesi necesita rendimiento. No , no utilizar ninguna otra cosa que no sea String.Substringen combinación conConvert.ToByte , a menos que alguien tiene algo interesante que decir acerca de esto en los comentarios de esta respuesta.

advertencia: esta respuesta puede volverse obsoleta si unConvert.ToByte(char[], Int32) se implementa sobrecarga en el marco. Es poco probable que esto suceda pronto.

Como regla general, no me gusta mucho decir "no optimices prematuramente", porque nadie sabe cuándo es "prematuro". Lo único que debe tener en cuenta al decidir si optimizar o no es: "¿Tengo el tiempo y los recursos para investigar los enfoques de optimización correctamente?". Si no lo hace, entonces es demasiado pronto, espere hasta que su proyecto es más maduro o hasta que necesite el rendimiento (si hay una necesidad real, entonces usted va a hacer que el tiempo). Mientras tanto, haga lo más simple que podría funcionar en su lugar.

Código original:

    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.Substringy utiliza un StringReaderen su lugar. La razón dada es:

Editar: puede mejorar el rendimiento de cadenas largas utilizando un analizador de un solo paso, de esta manera:

Bueno, mirando el código de referenciaString.Substring , ya es claramente "de un solo paso"; y por qué no debería ser? Funciona a nivel de byte, no en pares sustitutos.

Sin embargo, sí asigna una nueva cadena, pero luego debe asignar una para pasar de Convert.ToBytetodos modos. Además, la solución proporcionada en la revisión asigna otro objeto más en cada iteración (la matriz de dos caracteres); puede colocar esa asignación de forma segura fuera del ciclo y reutilizar la matriz para evitar eso.

    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 numeralrepresenta un solo octeto con dos dígitos (símbolos).

Pero entonces, ¿por qué llamar StringReader.Readdos veces? Simplemente llame a su segunda sobrecarga y pídale que lea dos caracteres en la matriz de dos caracteres a la vez; y reduzca la cantidad de llamadas en dos.

    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, jpor ejemplo), una variable de longitud redundante (interna _length) y una referencia redundante a la entrada cadena (interna _s). En otras palabras, es inútil.

Si se pregunta cómo Read"lee", solo mire el código , todo lo que hace es llamar String.CopyToa la cadena de entrada. El resto es solo gastos generales de contabilidad para mantener valores que no necesitamos.

Por lo tanto, elimine el lector de cadenas y llámese CopyTousted mismo; Es más simple, más claro y más eficiente.

    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 ipor 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.Substringpara asignar la cadena y copiar los datos, está utilizando una matriz intermedia a la que copia los números hexadecimales, luego asigna la cadena usted mismo y copia los datos nuevamente desde la matriz y dentro de la cadena (cuando la pasa en el constructor de la cadena). La segunda copia podría optimizarse si la cadena ya está en el grupo interno, pero String.Substringtambién podrá evitarla en estos casos.

De hecho, si observa de String.Substringnuevo, verá que utiliza un conocimiento interno de bajo nivel de cómo se construyen las cadenas para asignar la cadena más rápido de lo que normalmente podría hacerlo, y alinea el mismo código utilizado CopyTodirectamente allí para evitar la llamada sobrecarga.

String.Substring

  • El peor de los casos: una asignación rápida, una copia rápida.
  • Mejor caso: sin asignación, sin copia.

Método manual

  • Peor de los casos: dos asignaciones normales, una copia normal, una copia rápida.
  • Mejor de los casos: una asignación normal, una copia normal.

¿Conclusión? Si desea usarConvert.ToByte(String, Int32) (porque no quiere volver a implementar esa funcionalidad usted mismo), no parece haber una manera de vencer String.Substring; todo lo que haces es correr en círculos, reinventando la rueda (solo con materiales subóptimos).

Tenga en cuenta que usar Convert.ToBytey String.Substringes una opción perfectamente válida si no necesita un rendimiento extremo. Recuerde: solo opte por una alternativa si tiene el tiempo y los recursos para investigar cómo funciona correctamente.

Si hubiera un Convert.ToByte(char[], Int32), las cosas serían diferentes, por supuesto (sería posible hacer lo que describí anteriormente y evitar por completo String).

Sospecho que las personas que informan un mejor rendimiento al "evitar String.Substring" también evitan Convert.ToByte(String, Int32), lo que realmente debería estar haciendo si necesita el rendimiento de todos modos. Mira las innumerables otras respuestas para descubrir todos los diferentes enfoques para hacerlo.

Descargo de responsabilidad: no he descompilado la última versión del marco para verificar que la fuente de referencia esté actualizada, supongo que sí.

Ahora, todo suena bien y lógico, con suerte incluso obvio si has logrado llegar tan lejos. Pero es verdad?

Intel(R) Core(TM) i7-3720QM CPU @ 2.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).

tne
fuente
error: {"No se pudo encontrar ningún dígito reconocible".}
Priya Jagtap
17

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 0usando & 0x0f.

65 es código para 'A'

48 es código para '0'

7 es el número de letras entre '9'y 'A'en la tabla ASCII ( ...456789:;<=>?@ABCD...).

CoperNick
fuente
16

Este problema también podría resolverse utilizando una tabla de búsqueda. Esto requeriría una pequeña cantidad de memoria estática tanto para el codificador como para el decodificador. Sin embargo, este método será rápido:

  • Tabla de codificador 512 bytes o 1024 bytes (dos veces el tamaño si se necesitan mayúsculas y minúsculas)
  • Tabla de decodificadores de 256 bytes o 64 KiB (ya sea una búsqueda de un solo carácter o una búsqueda de dos caracteres)

Mi solución usa 1024 bytes para la tabla de codificación y 256 bytes para la decodificación.

Descodificación

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, 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.

drphrozen
fuente
2
El uso de memoria de 256 bytes es insignificante cuando ejecuta código en el CLR.
dolmen
9

Esta es una gran publicación. Me gusta la solución de Waleed. No lo he pasado por la prueba de Patridge, pero parece ser bastante rápido. También necesitaba el proceso inverso, convirtiendo una cadena hexadecimal en una matriz de bytes, así que lo escribí como una inversión de la solución de Waleed. No estoy seguro si es más rápido que la solución original de Tomalak. Nuevamente, tampoco ejecuté el proceso inverso a través de la prueba de Patridge.

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;
}
Chris F
fuente
Este código supone que la cadena hexadecimal usa caracteres alfabéticos en mayúsculas y explota si la cadena hexadecimal usa letras minúsculas alfa. Es posible que desee hacer una conversión "mayúscula" en la cadena de entrada para estar seguro.
Marc Novakowski el
Esa es una observación astuta Marc. El código fue escrito para revertir la solución de Waleed. La llamada ToUpper ralentizaría un poco el algoritmo, pero le permitiría manejar caracteres alfabéticos en minúsculas.
Chris F
3
Convert.ToByte (topChar + bottomChar) se puede escribir como (byte) (topChar + bottomChar)
Amir Rezaei
Para manejar ambos casos sin una gran penalización de rendimiento,hexString[i] &= ~0x20;
Ben Voigt
9

¿Por qué hacerlo complejo? Esto es simple en Visual Studio 2008:

C#:

string hex = BitConverter.ToString(YourByteArray).Replace("-", "");

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Craig Poulton
fuente
2
La razón es el rendimiento, cuando necesita una solución de alto rendimiento. :)
Ricky
7

No para apilar las muchas respuestas aquí, pero encontré una implementación bastante óptima (~ 4.5 veces mejor de lo aceptado), directa del analizador de cadenas hexadecimales. Primero, salida de mis pruebas (el primer lote es mi implementación):

Give me that string:
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 unsafey moví la ifsecuencia (claramente redundante) de personaje a mordisco a otro método, pero este fue el más rápido.

(Admito que esto responde la mitad de la pregunta. Sentí que la conversión cadena-> byte [] estaba subrepresentada, mientras que el ángulo de cadena byte [] -> parece estar bien cubierto. Por lo tanto, esta respuesta).

Ben Mosher
fuente
1
Para los seguidores de Knuth: hice esto porque necesito analizar algunos miles de cadenas hexadecimales cada pocos minutos, por lo que es importante que sea lo más rápido posible (en el bucle interno, por así decirlo). La solución de Tomalak no es notablemente más lenta si no se producen muchos de estos análisis.
Ben Mosher
5

Versiones seguras:

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).

Maratius
fuente
5

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);
    }
Geografía
fuente
4

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).

Pure.Krome
fuente
Probablemente debería probar el código antes de ofrecerlo para una pregunta como esta.
jww
3

De los desarrolladores de Microsoft, una conversión simple y agradable:

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.

Mark
fuente
2
El iterador no es el principal problema de este código. Deberías hacer una referencia b.ToSting("X2").
dolmen
2

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("-", "");
}
Jack Straw
fuente
si Source == nullo Source.Length == 0tenemos un problema señor!
Andrei Krasutski
2

En términos de velocidad, esto parece ser mejor que nada aquí:

  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);
  }
Alexey Borzenkov
fuente
2

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;
}
Fredrik Hu
fuente
2

Esta versión de ByteArrayToHexViaByteManipulation podría ser más rápida.

De mis informes:

  • ByteArrayToHexViaByteManipulation3: 1,68 ticks promedio (más de 1000 carreras), 17,5X
  • ByteArrayToHexViaByteManipulation2: 1,73 ticks promedio (más de 1000 carreras), 16,9X
  • ByteArrayToHexViaByteManipulation: 2,90 ticks promedio (más de 1000 carreras), 10,1X
  • ByteArrayToHexViaLookupAndShift: 3,22 ticks promedio (más de 1000 carreras), 9,1X
  • ...

    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);
    }
JoseH
fuente
2

Entraré en esta competencia de violín de bits, ya que tengo una respuesta que también usa bit-fiddling para decodificar hexadecimales. Tenga en cuenta que el uso de matrices de caracteres puede ser aún más rápido ya que los StringBuildermétodos de llamada también tomarán tiempo.

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.

Maarten Bodewes
fuente
Hmm, realmente debería optimizar esto Char[]y usarlo Charinternamente en lugar de ints ...
Maarten Bodewes
Para C #, es preferible inicializar las variables donde se usan, en lugar de fuera del ciclo, para permitir que el compilador se optimice. Obtengo un rendimiento equivalente de cualquier manera.
Peteter
2

Para el rendimiento, iría con la solución drphrozens. Una pequeña optimización para el decodificador podría ser usar una tabla para cualquiera de los caracteres para deshacerse del "<< 4".

Claramente, las dos llamadas al método son costosas. Si se realiza algún tipo de verificación en los datos de entrada o salida (podría ser CRC, suma de verificación o lo que sea), if (b == 255)...podría omitirse y, por lo tanto, también el método llama por completo.

Usar offset++y en offsetlugar de offsety offset + 1podría dar algún beneficio teórico, pero sospecho que el compilador maneja esto mejor que yo.

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.

ClausAndersen
fuente
1

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;          
}
Stas Makutin
fuente
1

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
MCattle
fuente
1

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;
}
JJJ
fuente
1
HexStringToByteArray ("09") devuelve 0x02 que es malo
CoperNick
1

Otra forma es usar stackallocpara 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);
}
Kel
fuente
1

Aquí está mi oportunidad. He creado un par de clases de extensión para extender cadenas y bytes. En la prueba de archivos grandes, el rendimiento es comparable al Byte Manipulation 2.

El siguiente código para ToHexString es una implementación optimizada del algoritmo de búsqueda y cambio. Es casi idéntico al de Behrooz, pero resulta que usa un foreachpara iterar y un contador es más rápido que una indexación explícitafor .

Viene en segundo lugar detrás de Byte Manipulation 2 en mi máquina y es un código muy legible. Los siguientes resultados de la prueba también son de interés:

ToHexStringCharArrayWithCharArrayLookup: 41,589.69 ticks promedio (más de 1000 carreras), 1.5X ToHexStringCharArrayWithStringLookup: 50,764.06 ticks promedio (más de 1000 carreras), 1.2X ToHexStringStringBuilderWithCharArray12okup: 62 tj (62,87)

En base a los resultados anteriores, parece seguro concluir que:

  1. Las penalizaciones por indexar en una cadena para realizar la búsqueda frente a una matriz de caracteres son significativas en la prueba de archivos grandes.
  2. Las penalizaciones por usar un StringBuilder de capacidad conocida versus una matriz de caracteres de tamaño conocido para crear la cadena son aún más significativas.

Aquí está el código:

using System;

namespace ConversionExtensions
{
    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


JamieSee
fuente
1

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;
}
spacepille
fuente