hexadecimal - int to byte c#




¿Cómo convertir una matriz de bytes a una cadena hexadecimal y viceversa? (20)

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


Análisis de rendimiento

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

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

Renuncia

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

Resultados

  • Búsqueda por byte unsafe (a través de CodesInChaos) (agregada a prueba de repo por airbreather )
    • Texto: 4,727.85 (105.2X)
    • Sentencia: 0.28 (99.7X)
  • Búsqueda por byte (a través de CodesInChaos)
    • Texto: 10,853.96 (45.8X más rápido)
    • Sentencia: 0.65 (42.7X más rápido)
  • Manipulación de bytes 2 (a través de CodesInChaos)
    • Texto: 12,967.69 (38.4X más rápido)
    • Sentencia: 0.73 (37.9X más rápido)
  • Manipulación de bytes (a través de Waleed Eissa)
    • Texto: 16,856.64 (29.5X más rápido)
    • Sentencia: 0.70 (39.5X más rápido)
  • Búsqueda / cambio (a través de Nathan Moinvaziri)
    • Texto: 23,201.23 (21.4X más rápido)
    • Sentencia: 1.24 (22.3X más rápido)
  • Búsqueda por mordisco (a través de Brian Lambert)
    • Texto: 23,879.41 (20.8X más rápido)
    • Sentencia: 1.15 (23.9X más rápido)
  • BitConverter (a través de Tomalak)
    • Texto: 113,269.34 (4.4X más rápido)
    • Sentencia: 9.98 (2.8X más rápido)
  • {SoapHexBinary}.ToString (a través de Mykroft)
    • Texto: 178,601.39 (2.8X más rápido)
    • Sentencia: 10.68 (2.6X más rápido)
  • {byte}.ToString("X2") (usando foreach ) (derivado de la respuesta de Will Dean)
    • Texto: 308,805.38 (2.4X más rápido)
    • Sentencia: 16.89 (2.4X más rápido)
  • {byte}.ToString("X2") (usando {IEnumerable}.Aggregate , requiere System.Linq) (vía Mark)
    • Texto: 352,828.20 (2.1X más rápido)
    • Sentencia: 16.87 (2.4X más rápido)
  • Array.ConvertAll (usando string.Join ) (a través de Will Dean)
    • Texto: 675,451.57 (1.1X más rápido)
    • Sentencia: 17.95 (2.2X más rápido)
  • Array.ConvertAll (usando string.Concat , requiere .NET 4.0) (a través de Will Dean)
    • Texto: 752,078.70 (1.0X más rápido)
    • Sentencia: 18.28 (2.2X más rápido)
  • {StringBuilder}.AppendFormat (utilizando foreach ) (a través de Tomalak)
    • Texto: 672,115.77 (1.1X más rápido)
    • Sentencia: 36.82 (1.1X más rápido)
  • {StringBuilder}.AppendFormat (usando {IEnumerable}.Aggregate , requiere System.Linq) (derivado de la respuesta de Tomalak)
    • Texto: 718,380.63 (1.0X más rápido)
    • Sentencia: 39.71 (1.0X más rápido)

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

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

Código de prueba

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

  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 valor de retorno de TestCandidates en esa misma clase.
  3. Asegúrese de que está ejecutando la versión de entrada que desea, oración o texto, alternando los comentarios en GenerateTestInput en esa misma clase.
  4. Presione F5 y espere la salida (también se genera un volcado de HTML en la carpeta / bin).
static string ByteArrayToHexStringViaStringJoinArrayConvertAll(byte[] bytes) {
    return string.Join(string.Empty, Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaStringConcatArrayConvertAll(byte[] bytes) {
    return string.Concat(Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaBitConverter(byte[] bytes) {
    string hex = BitConverter.ToString(bytes);
    return hex.Replace("-", "");
}
static string ByteArrayToHexStringViaStringBuilderAggregateByteToString(byte[] bytes) {
    return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.Append(b.ToString("X2"))).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachByteToString(byte[] bytes) {
    StringBuilder hex = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes)
        hex.Append(b.ToString("X2"));
    return hex.ToString();
}
static string ByteArrayToHexStringViaStringBuilderAggregateAppendFormat(byte[] bytes) {
    return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.AppendFormat("{0:X2}", b)).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachAppendFormat(byte[] bytes) {
    StringBuilder hex = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes)
        hex.AppendFormat("{0:X2}", b);
    return hex.ToString();
}
static string ByteArrayToHexViaByteManipulation(byte[] bytes) {
    char[] c = new char[bytes.Length * 2];
    byte b;
    for (int i = 0; i < bytes.Length; i++) {
        b = ((byte)(bytes[i] >> 4));
        c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
        b = ((byte)(bytes[i] & 0xF));
        c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
    }
    return new string(c);
}
static string ByteArrayToHexViaByteManipulation2(byte[] bytes) {
    char[] c = new char[bytes.Length * 2];
    int b;
    for (int i = 0; i < bytes.Length; i++) {
        b = bytes[i] >> 4;
        c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
        b = bytes[i] & 0xF;
        c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
    }
    return new string(c);
}
static string ByteArrayToHexViaSoapHexBinary(byte[] bytes) {
    SoapHexBinary soapHexBinary = new SoapHexBinary(bytes);
    return soapHexBinary.ToString();
}
static string ByteArrayToHexViaLookupAndShift(byte[] bytes) {
    StringBuilder result = new StringBuilder(bytes.Length * 2);
    string hexAlphabet = "0123456789ABCDEF";
    foreach (byte b in bytes) {
        result.Append(hexAlphabet[(int)(b >> 4)]);
        result.Append(hexAlphabet[(int)(b & 0xF)]);
    }
    return result.ToString();
}
static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_Lookup32, GCHandleType.Pinned).AddrOfPinnedObject();
static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes) {
    var lookupP = _lookup32UnsafeP;
    var result = new string((char)0, bytes.Length * 2);
    fixed (byte* bytesP = bytes)
    fixed (char* resultP = result) {
        uint* resultP2 = (uint*)resultP;
        for (int i = 0; i < bytes.Length; i++) {
            resultP2[i] = lookupP[bytesP[i]];
        }
    }
    return result;
}
static uint[] _Lookup32 = Enumerable.Range(0, 255).Select(i => {
    string s = i.ToString("X2");
    return ((uint)s[0]) + ((uint)s[1] << 16);
}).ToArray();
static string ByteArrayToHexViaLookupPerByte(byte[] bytes) {
    var result = new char[bytes.Length * 2];
    for (int i = 0; i < bytes.Length; i++)
    {
        var val = _Lookup32[bytes[i]];
        result[2*i] = (char)val;
        result[2*i + 1] = (char) (val >> 16);
    }
    return new string(result);
}
static string ByteArrayToHexViaLookup(byte[] bytes) {
    string[] hexStringTable = new string[] {
        "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F",
        "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F",
        "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F",
        "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F",
        "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F",
        "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F",
        "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F",
        "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F",
        "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F",
        "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F",
        "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF",
        "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF",
        "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF",
        "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF",
        "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF",
        "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF",
    };
    StringBuilder result = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes) {
        result.Append(hexStringTable[b]);
    }
    return result.ToString();
}

Actualización (2010-01-13)

Se agregó la respuesta de Waleed al análisis. Bastante rapido.

Actualización (2011-10-05)

Se string.Concat variante string.Concat Array.ConvertAll para completar (requiere .NET 4.0). A la par con string.Join version.

Actualización (2012-02-05)

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

Actualización (2012-04-03)

Se agregó la respuesta de Mykroft's SoapHexBinary al análisis, que tomó el tercer lugar.

Actualización (2013-01-15)

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

Actualización (2013-05-23)

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

Actualización (2014-07-31)

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

Actualización (2015-08-20)

Se airbreather optimizaciones airbreather y una variante unsafe al repositorio de esta respuesta . Si quieres jugar en el juego inseguro, puedes obtener grandes ganancias de rendimiento sobre cualquiera de los ganadores anteriores de cadenas cortas y textos grandes.


Acabo de encontrar el mismo problema hoy, y encontré este código:

private static string ByteArrayToHex(byte[] barray)
{
    char[] c = new char[barray.Length * 2];
    byte b;
    for (int i = 0; i < barray.Length; ++i)
    {
        b = ((byte)(barray[i] >> 4));
        c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
        b = ((byte)(barray[i] & 0xF));
        c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
    }
    return new string(c);
}

Fuente: byte [] del foro [] Array to Hex String (ver el post de PZahra). Modifiqué un poco el código para eliminar el prefijo 0x.

Hice algunas pruebas de rendimiento del código y fue casi ocho veces más rápido que usando BitConverter.ToString () (el más rápido según la publicación de patridge).


Esta es una respuesta a la revisión 4 de share de share (y ediciones posteriores).

Haré caso de que esta edición sea incorrecta y explicaré por qué podría revertirse. A lo largo del camino, podría aprender una o dos cosas sobre algunos aspectos internos y ver otro ejemplo de lo que realmente es la optimización prematura y cómo puede morderlo.

tl; dr: Solo use Convert.ToByte y String.Substring si tiene prisa ("Código original" a continuación), es la mejor combinación si no desea volver a implementar Convert.ToByte . Use algo más avanzado (ver otras respuestas) que no use Convert.ToByte si necesita rendimiento. No use nada más que String.Substring en combinación con Convert.ToByte , a menos que alguien tenga algo interesante que decir sobre esto en los comentarios de esta respuesta.

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

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

Código original:

    public static byte[] HexadecimalStringToByteArray_Original(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        for (var i = 0; i < outputLength; i++)
            output[i] = Convert.ToByte(input.Substring(i * 2, 2), 16);
        return output;
    }

Revisión 4:

    public static byte[] HexadecimalStringToByteArray_Rev4(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
                output[i] = Convert.ToByte(new string(new char[2] { (char)sr.Read(), (char)sr.Read() }), 16);
        }
        return output;
    }

La revisión evita String.Substring y usa un StringReader en StringReader lugar. La razón dada es:

Edición: puede mejorar el rendimiento de cadenas largas mediante el uso de un analizador de un solo paso, así:

Bueno, mirando el código de referencia para String.Substring , ya está claramente "un solo paso"; ¿Y por qué no debería ser? Funciona a nivel de byte, no en pares sustitutos.

Sin embargo, sí asigna una nueva cadena, pero de todos modos debe asignar una para pasarla a Convert.ToByte . Además, la solución provista en la revisión asigna otro objeto en cada iteración (la matriz de dos caracteres); puede poner esa asignación de manera segura fuera del bucle y reutilizar la matriz para evitar eso.

    public static byte[] HexadecimalStringToByteArray(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
            {
                numeral[0] = (char)sr.Read();
                numeral[1] = (char)sr.Read();
                output[i] = Convert.ToByte(new string(numeral), 16);
            }
        }
        return output;
    }

Cada numeral hexadecimal representa un solo octeto utilizando dos dígitos (símbolos).

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

    public static byte[] HexadecimalStringToByteArray(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
            {
                var read = sr.Read(numeral, 0, 2);
                Debug.Assert(read == 2);
                output[i] = Convert.ToByte(new string(numeral), 16);
            }
        }
        return output;
    }

Lo que queda es un lector de cadenas cuyo único "valor" agregado es un índice paralelo (interno _pos) que podría haberse declarado (como 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 son solo gastos generales de contabilidad para mantener los valores que no necesitamos.

Entonces, elimina el lector de cuerdas y llámate a CopyToti mismo; Es más simple, más claro y más eficiente.

    public static byte[] HexadecimalStringToByteArray(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        for (int i = 0, j = 0; i < outputLength; i++, j += 2)
        {
            input.CopyTo(j, numeral, 0, 2);
            output[i] = Convert.ToByte(new string(numeral), 16);
        }
        return output;
    }

¿Realmente necesitas un jíndice que se incremente en pasos de dos paralelos i? Por supuesto que no, solo multiplica ipor dos (lo que el compilador debería poder optimizar para una adición)

    public static byte[] HexadecimalStringToByteArray_BestEffort(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        for (int i = 0; i < outputLength; i++)
        {
            input.CopyTo(i * 2, numeral, 0, 2);
            output[i] = Convert.ToByte(new string(numeral), 16);
        }
        return output;
    }

¿Cómo se ve la solución ahora? Exactamente como era al principio, solo que en lugar de usarlo String.Substringpara asignar la cadena y copiarle los datos, está utilizando una matriz intermedia a la que copia los números hexadecimales, luego asigna la cadena usted mismo y vuelve a copiar los datos de la matriz y en la cadena (cuando se pasa en el constructor de cadena). La segunda copia podría optimizarse si la cadena ya está en el grupo interno, pero String.Substringtambién podrá evitarla en estos casos.

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

String.Substring

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

Método manual

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

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

Tenga en cuenta que usar Convert.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.

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

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

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

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

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

¡Sí!

Apoyos a Partridge para el marco del banco, es fácil de hackear. La entrada utilizada es el siguiente hash SHA-1 repetido 5000 veces para formar una cadena de 100,000 bytes.

209113288F93A9AB8E474EA78D899AFDBB874355

¡Que te diviertas! (Pero optimizar con moderación.)


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

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

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

Descodificación

private static readonly byte[] LookupTable = new byte[] {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

private static byte Lookup(char c)
{
  var b = LookupTable[c];
  if (b == 255)
    throw new IOException("Expected a hex character, got " + c);
  return b;
}

public static byte ToByte(char[] chars, int offset)
{
  return (byte)(Lookup(chars[offset]) << 4 | Lookup(chars[offset + 1]));
}

Codificación

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

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

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

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

Comparación

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

* esta solución

Nota

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


Otro enfoque basado en la tabla de búsqueda. Esta utiliza solo una tabla de búsqueda para cada byte, en lugar de una tabla de búsqueda por nibble.

private static readonly uint[] _lookup32 = CreateLookup32();

private static uint[] CreateLookup32()
{
    var result = new uint[256];
    for (int i = 0; i < 256; i++)
    {
        string s=i.ToString("X2");
        result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
    }
    return result;
}

private static string ByteArrayToHexViaLookup32(byte[] bytes)
{
    var lookup32 = _lookup32;
    var result = new char[bytes.Length * 2];
    for (int i = 0; i < bytes.Length; i++)
    {
        var val = lookup32[bytes[i]];
        result[2*i] = (char)val;
        result[2*i + 1] = (char) (val >> 16);
    }
    return new string(result);
}

También probé variantes de esto usando ushort , struct{char X1, X2} , struct{byte X1, X2} en la tabla de búsqueda.

Dependiendo del objetivo de compilación (x86, X64), estos tuvieron el mismo rendimiento o fueron ligeramente más lentos que esta variante.

Y para un rendimiento aún mayor, su hermano unsafe :

private static readonly uint[] _lookup32Unsafe = CreateLookup32Unsafe();
private static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();

private static uint[] CreateLookup32Unsafe()
{
    var result = new uint[256];
    for (int i = 0; i < 256; i++)
    {
        string s=i.ToString("X2");
        if(BitConverter.IsLittleEndian)
            result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
        else
            result[i] = ((uint)s[1]) + ((uint)s[0] << 16);
    }
    return result;
}

public static string ByteArrayToHexViaLookup32Unsafe(byte[] bytes)
{
    var lookupP = _lookup32UnsafeP;
    var result = new char[bytes.Length * 2];
    fixed(byte* bytesP = bytes)
    fixed (char* resultP = result)
    {
        uint* resultP2 = (uint*)resultP;
        for (int i = 0; i < bytes.Length; i++)
        {
            resultP2[i] = lookupP[bytesP[i]];
        }
    }
    return new string(result);
}

O si consideras aceptable escribir directamente en la cadena:

public static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes)
{
    var lookupP = _lookup32UnsafeP;
    var result = new string((char)0, bytes.Length * 2);
    fixed (byte* bytesP = bytes)
    fixed (char* resultP = result)
    {
        uint* resultP2 = (uint*)resultP;
        for (int i = 0; i < bytes.Length; i++)
        {
            resultP2[i] = lookupP[bytesP[i]];
        }
    }
    return result;
}

Puede utilizar el método BitConverter.ToString:

byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256}
Console.WriteLine( BitConverter.ToString(bytes));

Salida:

00-01-02-04-08-10-20-40-80-FF

Más información: BitConverter.ToString Method (Byte [])


Ya sea:

public static string ByteArrayToString(byte[] ba)
{
  StringBuilder hex = new StringBuilder(ba.Length * 2);
  foreach (byte b in ba)
    hex.AppendFormat("{0:x2}", b);
  return hex.ToString();
}

o:

public static string ByteArrayToString(byte[] ba)
{
  return BitConverter.ToString(ba).Replace("-","");
}

Hay incluso más variantes de hacerlo, por ejemplo here .

La conversión inversa sería así:

public static byte[] StringToByteArray(String hex)
{
  int NumberChars = hex.Length;
  byte[] bytes = new byte[NumberChars / 2];
  for (int i = 0; i < NumberChars; i += 2)
    bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
  return bytes;
}

Usar la Substring es la mejor opción en combinación con Convert.ToByte . Vea esta respuesta para más información. Si necesita un mejor rendimiento, debe evitar Convert.ToByte antes de poder eliminar SubString .


Métodos de extensión (descargo de responsabilidad: código completamente no probado, BTW ...):

public static class ByteExtensions
{
    public static string ToHexString(this byte[] ba)
    {
        StringBuilder hex = new StringBuilder(ba.Length * 2);

        foreach (byte b in ba)
        {
            hex.AppendFormat("{0:x2}", b);
        }
        return hex.ToString();
    }
}

etc. Utilice cualquiera de las tres soluciones de Tomalak (la última es un método de extensión en una cadena).


Complemento a responder por @CodesInChaos (método invertido)

public static byte[] HexToByteUsingByteManipulation(string s)
{
    byte[] bytes = new byte[s.Length / 2];
    for (int i = 0; i < bytes.Length; i++)
    {
        int hi = s[i*2] - 65;
        hi = hi + 10 + ((hi >> 31) & 7);

        int lo = s[i*2 + 1] - 65;
        lo = lo + 10 + ((lo >> 31) & 7) & 0x0f;

        bytes[i] = (byte) (lo | hi << 4);
    }
    return bytes;
}

Explicación:

& 0x0f Es para apoyar también letras minúsculas.

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

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

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

Para 'A' ... 'F' es hi = ch - 65 + 10;(esto se debe a 0x00000000 & 7).

Para 'a' ... 'f' tenemos números grandes, por lo que debemos restar 32 de la versión predeterminada haciendo algunos bits 0usando & 0x0f.

65 es el código para 'A'

48 es el código para '0'

7 es el número de letras entre '9'y 'A'en la tabla ASCII ( ...456789:;<=>[email protected]).


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

public static string ByteArrayToString(byte[] ba) 
{
    // Concatenate the bytes into one long string
    return ba.Aggregate(new StringBuilder(32),
                            (sb, b) => sb.Append(b.ToString("X2"))
                            ).ToString();
}

Mientras que lo anterior es limpio y compacto, los adictos al rendimiento gritarán al respecto utilizando enumeradores. Puede obtener el máximo rendimiento con una versión mejorada de la respuesta original de Tomolak:

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

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

   return hex.ToString();   
} 

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


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

  public static string ToHexString(byte[] data) {
    byte b;
    int i, j, k;
    int l = data.Length;
    char[] r = new char[l * 2];
    for (i = 0, j = 0; i < l; ++i) {
      b = data[i];
      k = b >> 4;
      r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
      k = b & 15;
      r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
    }
    return new string(r);
  }

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

De mis informes:

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

    static private readonly char[] hexAlphabet = new char[]
        {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    static string ByteArrayToHexViaByteManipulation3(byte[] bytes)
    {
        char[] c = new char[bytes.Length * 2];
        byte b;
        for (int i = 0; i < bytes.Length; i++)
        {
            b = ((byte)(bytes[i] >> 4));
            c[i * 2] = hexAlphabet[b];
            b = ((byte)(bytes[i] & 0xF));
            c[i * 2 + 1] = hexAlphabet[b];
        }
        return new string(c);
    }
    

Y creo que esta es una optimización:

    static private readonly char[] hexAlphabet = new char[]
        {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    static string ByteArrayToHexViaByteManipulation4(byte[] bytes)
    {
        char[] c = new char[bytes.Length * 2];
        for (int i = 0, ptr = 0; i < bytes.Length; i++, ptr += 2)
        {
            byte b = bytes[i];
            c[ptr] = hexAlphabet[b >> 4];
            c[ptr + 1] = hexAlphabet[b & 0xF];
        }
        return new string(c);
    }

No optimizado para la velocidad, pero más LINQy que la mayoría de las respuestas (.NET 4.0):

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

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

Otra función rápida ...

private static readonly byte[] HexNibble = new byte[] {
    0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
    0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
};

public static byte[] HexStringToByteArray( string str )
{
    int byteCount = str.Length >> 1;
    byte[] result = new byte[byteCount + (str.Length & 1)];
    for( int i = 0; i < byteCount; i++ )
        result[i] = (byte) (HexNibble[str[i << 1] - 48] << 4 | HexNibble[str[(i << 1) + 1] - 48]);
    if( (str.Length & 1) != 0 )
        result[byteCount] = (byte) HexNibble[str[str.Length - 1] - 48];
    return result;
}

Y para insertar en una cadena SQL (si no está usando parámetros de comando):

public static String ByteArrayToSQLHexString(byte[] Source)
{
    return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}

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

DO#:

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

VB:

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

Entraré en esta competencia de juguetear un poco ya que tengo una respuesta que también utiliza el violín de bits para decodificar hexadecimales. Tenga en cuenta que el uso de matrices de caracteres puede ser incluso más rápido, ya que los StringBuildermétodos de llamada también llevarán tiempo.

public static String ToHex (byte[] data)
{
    int dataLength = data.Length;
    // pre-create the stringbuilder using the length of the data * 2, precisely enough
    StringBuilder sb = new StringBuilder (dataLength * 2);
    for (int i = 0; i < dataLength; i++) {
        int b = data [i];

        // check using calculation over bits to see if first tuple is a letter
        // isLetter is zero if it is a digit, 1 if it is a letter
        int isLetter = (b >> 7) & ((b >> 6) | (b >> 5)) & 1;

        // calculate the code using a multiplication to make up the difference between
        // a digit character and an alphanumerical character
        int code = '0' + ((b >> 4) & 0xF) + isLetter * ('A' - '9' - 1);
        // now append the result, after casting the code point to a character
        sb.Append ((Char)code);

        // do the same with the lower (less significant) tuple
        isLetter = (b >> 3) & ((b >> 2) | (b >> 1)) & 1;
        code = '0' + (b & 0xF) + isLetter * ('A' - '9' - 1);
        sb.Append ((Char)code);
    }
    return sb.ToString ();
}

public static byte[] FromHex (String hex)
{

    // pre-create the array
    int resultLength = hex.Length / 2;
    byte[] result = new byte[resultLength];
    // set validity = 0 (0 = valid, anything else is not valid)
    int validity = 0;
    int c, isLetter, value, validDigitStruct, validDigit, validLetterStruct, validLetter;
    for (int i = 0, hexOffset = 0; i < resultLength; i++, hexOffset += 2) {
        c = hex [hexOffset];

        // check using calculation over bits to see if first char is a letter
        // isLetter is zero if it is a digit, 1 if it is a letter (upper & lowercase)
        isLetter = (c >> 6) & 1;

        // calculate the tuple value using a multiplication to make up the difference between
        // a digit character and an alphanumerical character
        // minus 1 for the fact that the letters are not zero based
        value = ((c & 0xF) + isLetter * (-1 + 10)) << 4;

        // check validity of all the other bits
        validity |= c >> 7; // changed to >>, maybe not OK, use UInt?

        validDigitStruct = (c & 0x30) ^ 0x30;
        validDigit = ((c & 0x8) >> 3) * (c & 0x6);
        validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);

        validLetterStruct = c & 0x18;
        validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
        validity |= isLetter * (validLetterStruct | validLetter);

        // do the same with the lower (less significant) tuple
        c = hex [hexOffset + 1];
        isLetter = (c >> 6) & 1;
        value ^= (c & 0xF) + isLetter * (-1 + 10);
        result [i] = (byte)value;

        // check validity of all the other bits
        validity |= c >> 7; // changed to >>, maybe not OK, use UInt?

        validDigitStruct = (c & 0x30) ^ 0x30;
        validDigit = ((c & 0x8) >> 3) * (c & 0x6);
        validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);

        validLetterStruct = c & 0x18;
        validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
        validity |= isLetter * (validLetterStruct | validLetter);
    }

    if (validity != 0) {
        throw new ArgumentException ("Hexadecimal encoding incorrect for input " + hex);
    }

    return result;
}

Convertido del código de Java.


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

private byte[] HexStringToByteArray(string hexString)
{
    int hexStringLength = hexString.Length;
    byte[] b = new byte[hexStringLength / 2];
    for (int i = 0; i < hexStringLength; i += 2)
    {
        int topChar = (hexString[i] > 0x40 ? hexString[i] - 0x37 : hexString[i] - 0x30) << 4;
        int bottomChar = hexString[i + 1] > 0x40 ? hexString[i + 1] - 0x37 : hexString[i + 1] - 0x30;
        b[i / 2] = Convert.ToByte(topChar + bottomChar);
    }
    return b;
}

No obtuve el código que sugieres para trabajar, Olipro. hex[i] + hex[i+1]aparentemente devolvió un int.

Sin embargo, tuve cierto éxito al tomar algunos consejos del código de Waleeds y armar esto juntos. Es feo como el infierno, pero parece funcionar y funciona en 1/3 del tiempo en comparación con los demás según mis pruebas (utilizando el mecanismo de prueba de patridges). Dependiendo del tamaño de entrada. Cambiar alrededor de?: S para separar primero 0-9 probablemente arrojaría un resultado un poco más rápido ya que hay más números que letras.

public static byte[] StringToByteArray2(string hex)
{
    byte[] bytes = new byte[hex.Length/2];
    int bl = bytes.Length;
    for (int i = 0; i < bl; ++i)
    {
        bytes[i] = (byte)((hex[2 * i] > 'F' ? hex[2 * i] - 0x57 : hex[2 * i] > '9' ? hex[2 * i] - 0x37 : hex[2 * i] - 0x30) << 4);
        bytes[i] |= (byte)(hex[2 * i + 1] > 'F' ? hex[2 * i + 1] - 0x57 : hex[2 * i + 1] > '9' ? hex[2 * i + 1] - 0x37 : hex[2 * i + 1] - 0x30);
    }
    return bytes;
}

Otra forma es usando 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);
}




hex