Java: ¿convertir una matriz de bytes en una cadena hexadecimal?
Tengo una matriz de bytes llena de números hexadecimales e imprimirla de manera fácil es bastante inútil porque hay muchos elementos no imprimibles. Lo que necesito es el código hexadecimal exacto en forma de:3a5f771c
De la discusión aquí , y especialmente de esta respuesta, esta es la función que uso actualmente:
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}
Mis propios pequeños puntos de referencia (un millón de bytes mil veces, 256 bytes 10 millones de veces) mostraron que era mucho más rápido que cualquier otra alternativa, aproximadamente la mitad del tiempo en matrices largas. En comparación con la respuesta que tomé, cambiar a operaciones bit a bit, como se sugirió en la discusión, redujo aproximadamente un 20% del tiempo para matrices largas. (Editar: cuando digo que es más rápido que las alternativas, me refiero al código alternativo ofrecido en las discusiones. El rendimiento es equivalente al Commons Codec, que usa un código muy similar).
Versión 2k20, con respecto a las cadenas compactas de Java 9:
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
public static String bytesToHex(byte[] bytes) {
byte[] hexChars = new byte[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars, StandardCharsets.UTF_8);
}
La biblioteca Apache Commons Codec tiene una clase Hex para realizar este tipo de trabajo.
import org.apache.commons.codec.binary.Hex;
String foo = "I am a string";
byte[] bytes = foo.getBytes();
System.out.println( Hex.encodeHexString( bytes ) );
El método javax.xml.bind.DatatypeConverter.printHexBinary()
, parte de la Arquitectura Java para Enlace XML (JAXB) , era una forma conveniente de convertir una byte[]
cadena en hexadecimal. La DatatypeConverter
clase también incluyó muchos otros métodos útiles de manipulación de datos.
En Java 8 y versiones anteriores, JAXB formaba parte de la biblioteca estándar de Java. Quedó obsoleto con Java 9 y eliminado con Java 11 , como parte de un esfuerzo por mover todos los paquetes de Java EE a sus propias bibliotecas. Es una larga historia . Ahora, javax.xml.bind
no existe, y si desea utilizar JAXB, que contiene DatatypeConverter
, deberá instalar la API JAXB y el tiempo de ejecución JAXB de Maven.
Uso de ejemplo:
byte bytes[] = {(byte)0, (byte)0, (byte)134, (byte)0, (byte)61};
String hex = javax.xml.bind.DatatypeConverter.printHexBinary(bytes);
Resultará en:
000086003D
Esta respuesta es la misma que esta .
La solución más simple, sin bibliotecas externas, sin constantes de dígitos:
public static String byteArrayToHex(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for(byte b: a)
sb.append(String.format("%02x", b));
return sb.toString();
}
A continuación se muestran algunas opciones comunes ordenadas desde simples (de una sola línea) hasta complejas (biblioteca enorme). Si está interesado en el rendimiento, consulte los microevaluaciones a continuación.
Opción 1: fragmento de código: simple (solo usando JDK/Android)
Opción 1a: BigInteger
Una solución muy sencilla es utilizar la BigInteger
representación hexadecimal:
new BigInteger(1, someByteArray).toString(16);
Tenga en cuenta que, dado que esto maneja números , no cadenas de bytes arbitrarias , omitirá los ceros a la izquierda; esto puede ser o no lo que desea (por ejemplo, 000AE3
frente 0AE3
a una entrada de 3 bytes). Esto también es muy lento, aproximadamente 100 veces más lento en comparación con la opción 2.
Opción 1b: Cadena.formato()
Usando el %X
marcador de posición, String.format()
es capaz de codificar la mayoría de los tipos primitivos ( short
,, ) en hexadecimal int
:long
String.format("%X", ByteBuffer.wrap(eightByteArray).getLong());
Opción 1c: Entero/Largo (solo matrices de 4/8 bytes)
Si tienes exclusivamente arrays de 4 bytes puedes utilizar el toHexString
método de la clase Integer:
Integer.toHexString(ByteBuffer.wrap(fourByteArray).getInt());
Lo mismo funciona con matrices de 8 bytes yLong
Long.toHexString(ByteBuffer.wrap(eightByteArray).getLong());
Opción 1d: formato hexadecimal JDK17+
Finalmente, JDK 17 ofrece soporte de primer nivel para codificación hexadecimal directa con HexFormat
:
HexFormat hex = HexFormat.of();
hex.formatHex(someByteArray)
Opción 2: fragmento de código: avanzado
Aquí hay un fragmento de código con todas las funciones, que se puede copiar y pegar, que admite mayúsculas/minúsculas y endianidad . Está optimizado para minimizar la complejidad de la memoria y maximizar el rendimiento y debería ser compatible con todas las versiones modernas de Java (5+).
private static final char[] LOOKUP_TABLE_LOWER = new char[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66};
private static final char[] LOOKUP_TABLE_UPPER = new char[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46};
public static String encode(byte[] byteArray, boolean upperCase, ByteOrder byteOrder) {
// our output size will be exactly 2x byte-array length
final char[] buffer = new char[byteArray.length * 2];
// choose lower or uppercase lookup table
final char[] lookup = upperCase ? LOOKUP_TABLE_UPPER : LOOKUP_TABLE_LOWER;
int index;
for (int i = 0; i < byteArray.length; i++) {
// for little endian we count from last to first
index = (byteOrder == ByteOrder.BIG_ENDIAN) ? i : byteArray.length - i - 1;
// extract the upper 4 bit and look up char (0-A)
buffer[i << 1] = lookup[(byteArray[index] >> 4) & 0xF];
// extract the lower 4 bit and look up char (0-A)
buffer[(i << 1) + 1] = lookup[(byteArray[index] & 0xF)];
}
return new String(buffer);
}
public static String encode(byte[] byteArray) {
return encode(byteArray, false, ByteOrder.BIG_ENDIAN);
}
El código fuente completo con licencia Apache v2 y decodificador se puede encontrar aquí .
Opción 3: usar una pequeña biblioteca optimizada: bytes-java
Mientras trabajaba en mi proyecto anterior, creé este pequeño conjunto de herramientas para trabajar con bytes en Java. No tiene dependencias externas y es compatible con Java 7+. Incluye, entre otros, un codificador/decodificador HEX muy rápido y bien probado:
import at.favre.lib.bytes.Bytes;
...
Bytes.wrap(someByteArray).encodeHex()
Puedes consultarlo en GitHub: bytes-java .
Opción 4: Códec Apache Commons
Por supuesto, están los buenos códecs comunes . ( opinión de advertencia más adelante ) Mientras trabajaba en el proyecto descrito anteriormente, analicé el código y quedé bastante decepcionado; una gran cantidad de código duplicado desorganizado, códecs obsoletos y exóticos probablemente solo sean útiles para muy pocas implementaciones lentas y con demasiada ingeniería de códecs populares (específicamente Base64). Por lo tanto, tomaría una decisión informada si desea utilizarlo o una alternativa. De todos modos, si todavía quieres usarlo, aquí tienes un fragmento de código:
import org.apache.commons.codec.binary.Hex;
...
Hex.encodeHexString(someByteArray));
Opción 5: Google Guayaba
La mayoría de las veces ya tienes la guayaba como dependencia. Si es así, simplemente use:
import com.google.common.io.BaseEncoding;
...
BaseEncoding.base16().lowerCase().encode(someByteArray);
Opción 6: Seguridad de primavera
Si usa el marco Spring con Spring Security , puede usar lo siguiente:
import org.springframework.security.crypto.codec.Hex
...
new String(Hex.encode(someByteArray));
Opción 7: Castillo Hinchable
Si ya utiliza el marco de seguridad Bouncy Castle, puede utilizar su Hex
utilidad:
import org.bouncycastle.util.encoders.Hex;
...
Hex.toHexString(someByteArray);
Realmente no es la opción 8: compatibilidad con Java 9+ o 'No utilizar JAXB javax/xml/bind/DatatypeConverter'
En versiones anteriores de Java (8 e inferiores), el código Java para JAXB se incluía como dependencia de tiempo de ejecución. Desde la modularización de Java 9 y Jigsaw, su código no puede acceder a otro código fuera de su módulo sin una declaración explícita. Así que tenga en cuenta si obtiene una excepción como:
java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
cuando se ejecuta en una JVM con Java 9+. Si es así, cambie las implementaciones a cualquiera de las alternativas anteriores. Véase también esta pregunta .
Micro puntos de referencia
Aquí se muestran los resultados de un simple micro benchmark JMH que codifica matrices de bytes de diferentes tamaños . Los valores son operaciones por segundo, por lo que cuanto más alto, mejor. Tenga en cuenta que los micro benchmarks muy a menudo no representan el comportamiento del mundo real, así que tome estos resultados con cautela.
| Name (ops/s) | 16 byte | 32 byte | 128 byte | 0.95 MB |
|----------------------|-----------:|-----------:|----------:|--------:|
| Opt1: BigInteger | 2,088,514 | 1,008,357 | 133,665 | 4 |
| Opt2/3: Bytes Lib | 20,423,170 | 16,049,841 | 6,685,522 | 825 |
| Opt4: Apache Commons | 17,503,857 | 12,382,018 | 4,319,898 | 529 |
| Opt5: Guava | 10,177,925 | 6,937,833 | 2,094,658 | 257 |
| Opt6: Spring | 18,704,986 | 13,643,374 | 4,904,805 | 601 |
| Opt7: BC | 7,501,666 | 3,674,422 | 1,077,236 | 152 |
| Opt8: JAX-B | 13,497,736 | 8,312,834 | 2,590,940 | 346 |
Especificaciones: JDK 8u202, i7-7700K, Win10, 24 GB de Ram. Vea el punto de referencia completo aquí .
Actualización de referencia 2022
Aquí se muestran los resultados con JMH 1.36 actual, Java 17 y una computadora de gama alta.
| Name (ops/s) | 16 byte | 32 byte | 128 byte | 0.95 MB |
|----------------------|-----------:|-----------:|----------:|--------:|
| Opt1a: BigInteger | 2,941,403 | 1,389,448 | 242,096 | 5 |
| Opt1d: HexFormat | 32,635,184 | 20,262,332 | 7,388,135 | 922 |
| Opt2/3: Bytes Lib | 31,724,981 | 22,786,906 | 6,197,028 | 930 |
Especificaciones: JDK temurin 17.0.6, Ryzen 5900X, Win11, 24 GB DDR4 Ram