¿Cómo convertir un conjunto de bytes en una cadena hexadecimal, y viceversa?

StackOverflow https://stackoverflow.com/questions/311165

  •  10-07-2019
  •  | 
  •  

Pregunta

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

¿Fue útil?

Solución

Cualquiera:

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. Consulte esta respuesta para obtener más información. Si necesita un mejor rendimiento, debe evitar SubString antes de poder soltar <=>.

Otros consejos

Análisis de rendimiento

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

Ejecuté cada uno de los diversos métodos de conversión a través de algunas pruebas de rendimiento crudas Stopwatch, 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 implementación [más lenta] StringBuilder. Para el código utilizado, consulte a continuación o el test framework repo 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 precalcular lo que cualquier mordisco o byte 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éntete libre de jugar con el código de prueba que utilicé. Aquí se incluye una versión, pero puede clonar el repo 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 (<=>) a /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Agregue el nombre de ese método al <=> valor de retorno en esa misma clase.
  3. Asegúrese de ejecutar la versión de entrada que desea, frase o texto, alternando los comentarios en <=> en esa misma clase.
  4. Presione F5 y espere la salida (también se genera un volcado 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. Muy rápido.

Actualización (2011-10-05)

Se agregó la variante <=> <=> para completar (requiere .NET 4.0). A la par con <=> versión.

Actualización (2012-02-05)

El repositorio de prueba incluye más variantes, como <=>. Ninguno alteró los resultados. <=> es más rápido que <=>, por ejemplo, pero <=> aún gana.

Actualización (2012-04-03)

Se agregó la <=> respuesta de Mykroft al análisis, que ocupó 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 de airbreather y <=> variante a esta repositorio de respuestas . 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.

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();
}

Cuando la escritura crypto código es común para evitar que los datos dependientes de las ramas y las búsquedas en una tabla para asegurarse de que el tiempo de ejecución no depende de los datos, ya que los datos dependientes del tiempo 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


Abandonad toda esperanza, los que entráis aquí

Una explicación de la extraña poco de tocar el violín:

  1. bytes[i] >> 4 extractos de el alto nibble de un byte
    bytes[i] & 0xF extrae el nibble bajo de un byte
  2. b - 10
    es < 0 para los valores b < 10, que se convertirá en un dígito decimal
    es >= 0 para los valores b > 10, que se convertirá en una carta de A a F.
  3. El uso de i >> 31 en un entero de 32 bits con signo extractos de la señal, gracias a la señal de extensión.Será -1 para i < 0 y 0 para i >= 0.
  4. La combinación de 2) y 3), muestra que (b-10)>>31 será 0 para las cartas y -1 para dígitos.
  5. Mirando en el caso de las letras, el último sumando convierte en 0, y b es en el rango de 10 a 15.Queremos hacer un mapa para A(65) F(70), lo que implica la adición de 55 ('A'-10).
  6. Mirando en el caso de los dígitos, queremos adaptar el último sumando así que los mapas b en el rango de 0 a 9 para el rango de 0(48) a 9(57).Esto significa que se necesita para convertirse en -7 ('0' - 55).
    Ahora podríamos multiplicar con 7.Pero desde -1 está representado por todos los bits a 1, se pueden utilizar en su lugar & -7 desde (0 & -7) == 0 y (-1 & -7) == -7.

Algunas consideraciones adicionales:

  • Yo no uso un segundo bucle de la variable de índice en c, ya que la medida pone de manifiesto que el cálculo de i es más barato.
  • Utilizando exactamente i < bytes.Length como límite superior del bucle permite que el JITter para eliminar los límites de cheques en bytes[i], he escogido esta variante.
  • Hacer b int permite conversiones innecesarias de y a byte.

Si quiere más flexibilidad que BitConverter, pero no quiere esos lazos explícitos torpes de los años 90, entonces puede hacer:

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 probé variantes de esto usando ushort, struct{char X1, X2}, struct{byte X1, X2} en la tabla de búsqueda.

Dependiendo del objetivo de compilación (x86, X64), 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;
}

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: Publicación del foro byte [] Array to Hex String (vea 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).

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 & nbsp; bytes o 1024 & nbsp; bytes (dos veces el tamaño si tanto mayúsculas como minúsculas es necesario)
  • Tabla de decodificadores 256 & nbsp; bytes o 64 & Nbsp; KiB (una sola búsqueda de caracteres o búsqueda de doble carácter)

Mi solución utiliza 1024 & nbsp; bytes para la tabla de codificación y 256 & nbsp; bytes para la decodificación.

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]));
}

Codificación

private static readonly char[][] LookupTableUpper;
private static readonly char[][] LookupTableLower;

static Hex()
{
  LookupTableLower = new char[256][];
  LookupTableUpper = new char[256][];
  for (var i = 0; i < 256; i++)
  {
    LookupTableLower[i] = i.ToString("x2").ToCharArray();
    LookupTableUpper[i] = i.ToString("X2").ToCharArray();
  }
}

public static char[] ToCharLower(byte[] b, int bOffset)
{
  return LookupTableLower[b[bOffset]];
}

public static char[] ToCharUpper(byte[] b, int bOffset)
{
  return LookupTableUpper[b[bOffset]];
}

Comparación

StringBuilderToStringFromBytes:   106148
BitConverterToStringFromBytes:     15783
ArrayConvertAllToStringFromBytes:  54290
ByteManipulationToCharArray:        8444
TableBasedToCharArray:              5651 *

* esta solución

Nota

Durante la decodificación, se puede producir IOException e IndexOutOfRangeException (si un carácter tiene un valor demasiado alto > 256). Deben implementarse métodos para descodificar secuencias o matrices, esto es solo una prueba de concepto.

Esta es una respuesta a revisión 4 de Tomalak muy populares respuesta (y posteriores modificaciones).

Me voy a dar el caso de que esta edición está mal, y explicar por qué se podría revertir.A lo largo del camino, usted podría aprender una cosa o dos acerca de algunos aspectos internos y ver otro ejemplo de lo prematuro de optimización que realmente es y cómo se puede morder.

tl;dr: Sólo uso Convert.ToByte y String.Substring si estás en un apuro ("código Original" a continuación), es la mejor combinación, si usted no quiere volver a implementar Convert.ToByte.Uso algo más avanzado (ver otras respuestas) que no uso Convert.ToByte si necesidad rendimiento.¿ no usar otra cosa que no sea String.Substring en combinación con Convert.ToByte, a menos que alguien tenga algo interesante que decir sobre esto en los comentarios de esta respuesta.

advertencia: Esta respuesta puede llegar a ser obsoleta si un Convert.ToByte(char[], Int32) la sobrecarga es el implementado en el marco.Es poco probable que esto suceda pronto.

Como regla general, no me gusta mucho decir "no optimizar prematuramente", porque nadie sabe cuando "prematuro" es.La única cosa que usted debe considerar al decidir si se debe optimizar o no es:"¿Tengo el tiempo y los recursos para investigar la optimización de los enfoques correctamente?".Si no, es demasiado pronto, espere hasta que su proyecto es más maduro, o hasta que la necesidad de la actuación (si es que hay una necesidad real, entonces usted va a hacer el tiempo).Mientras tanto, hacer la cosa más simple que eso 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;
    }

Versió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 una StringReader en su lugar.La razón dada es:

Editar:usted puede mejorar el rendimiento de cadenas largas con una sola pasar analizador, así:

Así, mirando a la código de referencia para String.Substring, es claramente "un solo paso" ya;y por qué no debería ser?Funciona a nivel de byte, no en los pares suplentes.

No asignar una nueva cadena, sin embargo, pero, a continuación, tendrá que asignar un pase a Convert.ToByte de todos modos.Además, la solución que se ofrece en la revisión asigna otro objeto en cada iteración (los dos-matriz de char);usted puede de manera segura puesto que la asignación de fuera del bucle y la reutilización de la matriz para evitar que.

    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 único octeto utilizando dos dígitos (símbolos).

Pero entonces, ¿por qué llamar a StringReader.Read dos veces?Simplemente llame a su segunda sobrecarga y pedirle que lea dos personajes de la dos-matriz de char a la vez;y reducir la cantidad de llamadas por 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 una cadena lector cuyo único agregó que "valor" es un índice paralelo (interno _pos) que usted podría haber declarado a sí mismo (como j por ejemplo), un redundantes de longitud variable (interno _length), y redundante referencia a la cadena de entrada (interno _s).En otras palabras, es inútil.

Si usted se pregunta cómo Read "lee", con tan sólo mirar en el código, todo lo que hace es llamar a String.CopyTo en la cadena de entrada.El resto es sólo libro de mantenimiento de la sobrecarga para mantener los valores que no necesitamos.

Por lo tanto, quitar la cadena lector ya, y llame a 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;
    }

¿Usted realmente necesita un j índice que se incrementa en pasos de dos en paralelo a i?Por supuesto que no, simplemente multiplique i por dos (que el compilador debe ser capaz de 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;
    }

Lo que hace la solución parecen ahora?Exactamente igual a como era en el principio, sólo que en lugar de utilizar String.Substring para asignar la cadena y copiar los datos que se esté utilizando un intermediario de la matriz a la que copiar los números hexadecimales para, a continuación, asignar la cadena y copiar los datos de nuevo a partir de la matriz y en la cadena (cuando se pasa en el constructor de cadena).La segunda copia puede ser optimizado si la cadena ya está en el interno de la piscina, pero luego String.Substring también será capaz de evitar en estos casos.

De hecho, si usted mira en String.Substring de nuevo, se ve que se usa un bajo nivel de conocimiento interno de cómo las cadenas están construidos para asignar la cadena más rápido de lo que normalmente podría hacerlo, y elementos incorporados en el mismo código utilizado por CopyTo directamente allí para evitar la sobrecarga de la llamada.

String.Substring

  • Peor de los casos:Una rápida asignación, una copia rápida.
  • El mejor de los casos:De No asignación, no copiar.

Método Manual

  • Peor de los casos:Dos normales asignaciones, una copia normal, una copia rápida.
  • El mejor de los casos:Uno normal de asignación, una copia normal.

Conclusión? Si desea utilizar Convert.ToByte(String, Int32) (porque no quiere volver a implementar la funcionalidad de sí mismo), no parece ser una manera de vencer String.Substring;todo lo que hacemos es correr en círculos, re-inventar la rueda, sólo con la sub-óptimo de los materiales).

Tenga en cuenta que el uso de Convert.ToByte y String.Substring es una perfecta opción válida si no necesita un rendimiento extremo.Recuerde:sólo optar por una alternativa si usted tiene el tiempo y los recursos para investigar cómo funciona adecuadamente.

Si hubo un Convert.ToByte(char[], Int32), las cosas serían diferentes de curso (sería posible hacer lo que he descrito anteriormente y evitar por completo el String).

Sospecho que la gente que se informe mejor rendimiento por "evitar la String.Substring"también evitar Convert.ToByte(String, Int32), que realmente debería estar haciendo si usted necesita el rendimiento de todos modos.Mirar a los innumerables otras respuestas a descubrir todos los diferentes enfoques para hacer eso.

Descargo de responsabilidad:No he descompilados de la última versión del marco de trabajo para verificar que la fuente de referencia es hasta la fecha, supongo que es.

Ahora, todo parece bien y lógico, con suerte, incluso obvio si se ha logrado llegar tan lejos.Pero, ¿es cierto?

Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz
    Cores: 8
    Current Clock Speed: 2600
    Max Clock Speed: 2600
--------------------
Parsing hexadecimal string into an array of bytes
--------------------
HexadecimalStringToByteArray_Original: 7,777.09 average ticks (over 10000 runs), 1.2X
HexadecimalStringToByteArray_BestEffort: 8,550.82 average ticks (over 10000 runs), 1.1X
HexadecimalStringToByteArray_Rev4: 9,218.03 average ticks (over 10000 runs), 1.0X

Sí!

Apoyos a la Perdiz por el banco de marco, es fácil de hackear.La entrada es la siguiente hash SHA-1 repite 5000 veces para hacer un 100.000 bytes de cadena larga.

209113288F93A9AB8E474EA78D899AFDBB874355

Divertirse!(Pero optimizar con moderación.)

Complemento a la respuesta de @CodesInChaos (invertido método)

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 además, el apoyo de las minúsculas

hi = hi + 10 + ((hi >> 31) & 7); es lo mismo que:

hi = ch-65 + 10 + (((ch-65) >> 31) & 7);

Para '0'..'9' es el mismo de la hi = ch - 65 + 10 + 7; que es hi = ch - 48 (esto es debido a que de 0xffffffff & 7).

'A'..'F' es hi = ch - 65 + 10; (esto es debido a que de 0x00000000 & 7).

'A'..'f' tenemos a los grandes números, por lo que debemos restar 32 de la versión predeterminada haciendo algunos bits 0 mediante el uso de & 0x0f.

65 código es de 'A'

48 código es de '0'

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

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, convertir 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;
}

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

C #:

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

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")

No a la pila en la que las muchas respuestas aquí, pero he encontrado una bastante óptimo (~4.5 x mejor que aceptan), aplicación sencilla de la cadena hexadecimal analizador.Primero, los resultados de mis pruebas (el primer lote es mi aplicació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

El base64 y 'BitConverter d' líneas están allí para probar para la corrección.Tenga en cuenta que son iguales.

La aplicació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;
}

He intentado algunas cosas con unsafe y moviendo el (claramente redundante) carácter a picar if secuencia a otro método, pero esta fue la más rápida de la que tuvo.

(Reconozco que esto responda a la mitad de la pregunta.Sentí que la cadena->byte[] en la conversión fue representados, mientras que el byte[]->string ángulo 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;
        }
    }
}

BTW 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 de 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 bytes.

Y, por supuesto, la prueba debe compilarse en Release (con optimización) y con la opción de depuración " Suprimir la optimización JIT " desactivado (lo mismo para " Habilitar Just My Code " si el código debe ser depurable).

Función inversa para el código Waleed Eissa (cadena hexadecimal a matriz de bytes):

    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. Utiliza 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 Tomolak:

public static string ByteArrayToString(byte[] ba)   
{   
   StringBuilder hex = new StringBuilder(ba.Length * 2);   

   for(int i=0; i < ga.Length; i++)       // <-- Use for loop is faster than foreach   
       hex.Append(ba[i].ToString("X2"));   // <-- ToString is faster than AppendFormat   

   return hex.ToString();   
} 

Esta es la más rápida de todas las rutinas que he visto publicadas aquí hasta ahora. No solo confíe en mi palabra ... pruebe el rendimiento de cada rutina e inspeccione su código CIL por usted mismo.

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 recibí el código que sugirió que funcionara, Olipro. hex[i] + hex[i+1] aparentemente devolvió un int.

Sin embargo, tuve cierto éxito al tomar algunas pistas 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 los?: S para separar 0-9 primero probablemente producirí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:

  • 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);
    }

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 llamar a los métodos StringBuilder también llevará 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.

Para el rendimiento me gustaría ir con drphrozens solución.Una pequeña optimización para el decodificador podría ser la utilización de una mesa para el char para deshacerse de la "<< 4".

Claramente las dos llamadas a métodos son costosos.Si algún tipo de comprobación se realiza ya sea en la entrada o salida de datos (podría ser CRC, de la suma de comprobación o lo que sea) el if (b == 255)... podría ser omitido y por lo tanto también el método de las llamadas por completo.

El uso de offset++ y offset en lugar de offset y offset + 1 podría dar algún beneficio teórico pero sospecho que el compilador se encarga de 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 es sólo la parte superior de mi cabeza y no ha sido probado o punto de referencia.

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("-", "");
}

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 optimizado para la velocidad, pero más LINQy que la mayoría de las respuestas (.NET 4.0):

<Extension()>
Public Function FromHexToByteArray(hex As String) As Byte()
    hex = If(hex, String.Empty)
    If hex.Length Mod 2 = 1 Then hex = "0" & hex
    Return Enumerable.Range(0, hex.Length \ 2).Select(Function(i) Convert.ToByte(hex.Substring(i * 2, 2), 16)).ToArray
End Function

<Extension()>
Public Function ToHexString(bytes As IEnumerable(Of Byte)) As String
    Return String.Concat(bytes.Select(Function(b) b.ToString("X2")))
End Function

Dos mashups que pliegan las dos operaciones de mordisco en una sola.

Probablemente una 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 reversa:

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 utilizar 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 a la manipulación de bytes 2.

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

Viene en segundo lugar detrás de Byte Manipulation 2 en mi máquina y es un código muy legible. Los siguientes resultados de 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 ToHexStringStringBuilderWithCharArrayLookup: 62,812.87 ticks promedio (más de 1000 carreras), 1.0X

Según los resultados anteriores, parece seguro concluir que:

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

Aquí está el código:

using System;

namespace ConversionExtensions
{
    public static class ByteArrayExtensions
    {
        private readonly static char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

        public static string ToHexString(this byte[] bytes)
        {
            char[] hex = new char[bytes.Length * 2];
            int index = 0;

            foreach (byte b in bytes)
            {
                hex[index++] = digits[b >> 4];
                hex[index++] = digits[b & 0x0F];
            }

            return new string(hex);
        }
    }
}


using System;
using System.IO;

namespace ConversionExtensions
{
    public static class StringExtensions
    {
        public static byte[] ToBytes(this string hexString)
        {
            if (!string.IsNullOrEmpty(hexString) && hexString.Length % 2 != 0)
            {
                throw new FormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");
            }

            hexString = hexString.ToUpperInvariant();
            byte[] data = new byte[hexString.Length / 2];

            for (int index = 0; index < hexString.Length; index += 2)
            {
                int highDigitValue = hexString[index] <= '9' ? hexString[index] - '0' : hexString[index] - 'A' + 10;
                int lowDigitValue = hexString[index + 1] <= '9' ? hexString[index + 1] - '0' : hexString[index + 1] - 'A' + 10;

                if (highDigitValue < 0 || lowDigitValue < 0 || highDigitValue > 15 || lowDigitValue > 15)
                {
                    throw new FormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");
                }
                else
                {
                    byte value = (byte)((highDigitValue << 4) | (lowDigitValue & 0x0F));
                    data[index / 2] = value;
                }
            }

            return data;
        }
    }
}

A continuación se muestran los resultados de las pruebas que obtuve cuando puse mi código en el proyecto de 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/>
     
     

Conversión de 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   corre), 10.2X

     

ByteArrayToHexStringViaBitConverter: 139,353.73 promedio   garrapatas (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 promedio   garrapatas (más de 1000 carreras), 1.1X

     

ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867,303.98   ticks promedio (más de 1000 carreras), 1.0X

     

ByteArrayToHexStringViaStringJoinArrayConvertAll: 882,710.28 promedio   garrapatas (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;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top