Conversión entre matrices de bytes y cadenas hexadecimales en Java

1. Información general

En este tutorial, veremos diferentes formas de convertir una matriz de bytes en una cadena hexadecimal y viceversa.

También entenderemos el mecanismo de conversión y escribiremos nuestra implementación para lograrlo.

2. Conversión entre byte y hexadecimal

En primer lugar, echemos un vistazo a la lógica de conversión entre números de bytes y hexadecimales.

2.1. Byte a hexadecimal

Los bytes son enteros de 8 bits con signo en Java. Por lo tanto, necesitamos convertir cada segmento de 4 bits a hexadecimal por separado y concatenarlos . En consecuencia, obtendremos dos caracteres hexadecimales después de la conversión.

Por ejemplo, podemos escribir 45 como 0010 1101 en binario, y el equivalente hexadecimal será "2d":

0010 = 2 (base 10) = 2 (base 16) 1101 = 13 (base 10) = d (base 16) Therefore: 45 = 0010 1101 = 0x2d 

Implementemos esta lógica simple en Java:

public String byteToHex(byte num) { char[] hexDigits = new char[2]; hexDigits[0] = Character.forDigit((num >> 4) & 0xF, 16); hexDigits[1] = Character.forDigit((num & 0xF), 16); return new String(hexDigits); }

Ahora, entendamos el código anterior analizando cada operación. En primer lugar, creamos una matriz de caracteres de longitud 2 para almacenar la salida:

char[] hexDigits = new char[2];

A continuación, aislamos bits de orden superior desplazando 4 bits a la derecha. Y luego, aplicamos una máscara para aislar 4 bits de orden inferior. Se requiere enmascaramiento porque los números negativos se representan internamente como el complemento a dos del número positivo:

hexDigits[0] = Character.forDigit((num >> 4) & 0xF, 16);

Luego convertimos los 4 bits restantes a hexadecimal:

hexDigits[1] = Character.forDigit((num & 0xF), 16);

Finalmente, creamos un objeto String a partir de la matriz char. Y luego, devolvió este objeto como matriz hexadecimal convertida.

Ahora, entendamos cómo funcionará esto para un byte negativo -4:

hexDigits[0]: 1111 1100 >> 4 = 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 & 0xF = 0000 0000 0000 0000 0000 0000 0000 1111 = 0xf hexDigits[1]: 1111 1100 & 0xF = 0000 1100 = 0xc Therefore: -4 (base 10) = 1111 1100 (base 2) = fc (base 16)

También vale la pena señalar que el personaje. El método forDigit () siempre devuelve caracteres en minúscula.

2.2. Hexadecimal a Byte

Ahora, convierta un dígito hexadecimal en byte. Como sabemos, un byte contiene 8 bits. Por lo tanto, necesitamos dos dígitos hexadecimales para crear un byte .

En primer lugar, convertiremos cada dígito hexadecimal en equivalente binario por separado.

Y luego, necesitamos concatenar los dos segmentos de cuatro bits para obtener el equivalente en bytes:

Hexadecimal: 2d 2 = 0010 (base 2) d = 1101 (base 2) Therefore: 2d = 0010 1101 (base 2) = 45

Ahora, escribamos la operación en Java:

public byte hexToByte(String hexString) { int firstDigit = toDigit(hexString.charAt(0)); int secondDigit = toDigit(hexString.charAt(1)); return (byte) ((firstDigit << 4) + secondDigit); } private int toDigit(char hexChar) { int digit = Character.digit(hexChar, 16); if(digit == -1) { throw new IllegalArgumentException( "Invalid Hexadecimal Character: "+ hexChar); } return digit; }

Entendamos esto, una operación a la vez.

En primer lugar, convertimos caracteres hexadecimales en números enteros:

int firstDigit = toDigit(hexString.charAt(0)); int secondDigit = toDigit(hexString.charAt(1));

Luego dejamos desplazado el dígito más significativo en 4 bits. En consecuencia, la representación binaria tiene ceros en los cuatro bits menos significativos.

Luego, le agregamos el dígito menos significativo:

return (byte) ((firstDigit << 4) + secondDigit);

Ahora, examinemos el método toDigit () de cerca. Estamos usando el método Character.digit () para la conversión. Si el valor de carácter pasado a este método no es un dígito válido en la base especificada, se devuelve -1.

Estamos validando el valor de retorno y lanzando una excepción si se pasó un valor no válido.

3. Conversión entre matrices de bytes y cadenas hexadecimales

En este punto, sabemos cómo convertir un byte en hexadecimal y viceversa. Escalemos este algoritmo y convierta una matriz de bytes a / desde Cadena hexadecimal .

3.1. Matriz de bytes a cadena hexadecimal

Necesitamos recorrer la matriz y generar un par hexadecimal para cada byte:

public String encodeHexString(byte[] byteArray) { StringBuffer hexStringBuffer = new StringBuffer(); for (int i = 0; i < byteArray.length; i++) { hexStringBuffer.append(byteToHex(byteArray[i])); } return hexStringBuffer.toString(); }

Como ya sabemos, la salida siempre estará en minúsculas.

3.2. Cadena hexadecimal a matriz de bytes

En primer lugar, debemos comprobar si la longitud de la cadena hexadecimal es un número par. Esto se debe a que una cadena hexadecimal con una longitud impar dará como resultado una representación de bytes incorrecta.

Ahora, iteraremos a través de la matriz y convertiremos cada par hexadecimal en un byte:

public byte[] decodeHexString(String hexString) { if (hexString.length() % 2 == 1) { throw new IllegalArgumentException( "Invalid hexadecimal String supplied."); } byte[] bytes = new byte[hexString.length() / 2]; for (int i = 0; i < hexString.length(); i += 2) { bytes[i / 2] = hexToByte(hexString.substring(i, i + 2)); } return bytes; }

4. Usando la clase BigInteger

Podemos crear un objeto de tipo BigInteger pasando un signum y una matriz de bytes .

Ahora, podemos generar la cadena hexadecimal con la ayuda del formato de método estático definido en la clase String :

public String encodeUsingBigIntegerStringFormat(byte[] bytes) { BigInteger bigInteger = new BigInteger(1, bytes); return String.format( "%0" + (bytes.length << 1) + "x", bigInteger); }

The format provided will generate a zero-padded lowercase hexadecimal String. We can also generate an uppercase string by replacing “x” with “X”.

Alternatively, we could've used the toString() method from BigInteger. The subtle difference of using the toString() method is that the output isn't padded with leading zeros:

public String encodeUsingBigIntegerToString(byte[] bytes) { BigInteger bigInteger = new BigInteger(1, bytes); return bigInteger.toString(16); }

Now, let's take a look at hexadecimal String to byte Array conversion:

public byte[] decodeUsingBigInteger(String hexString) { byte[] byteArray = new BigInteger(hexString, 16) .toByteArray(); if (byteArray[0] == 0) { byte[] output = new byte[byteArray.length - 1]; System.arraycopy( byteArray, 1, output, 0, output.length); return output; } return byteArray; }

The toByteArray() method produces an additional sign bit. We have written specific code for handling this additional bit.

Hence, we should be aware of these details before using the BigInteger class for the conversion.

5. Using the DataTypeConverter Class

The DataTypeConverter class is supplied with JAXB library. This is part of the standard library until Java 8. Starting from Java 9, we need to add java.xml.bind module to the runtime explicitly.

Let's take a look at implementation using the DataTypeConverter class:

public String encodeUsingDataTypeConverter(byte[] bytes) { return DatatypeConverter.printHexBinary(bytes); } public byte[] decodeUsingDataTypeConverter(String hexString) { return DatatypeConverter.parseHexBinary(hexString); }

As displayed above, it is very convenient to use DataTypeConverter class. The output of the printHexBinary() method is always in uppercase. This class supplies a set of print and parse methods for data type conversion.

Before choosing this approach, we need to make sure the class will be available at runtime.

6. Using Apache's Commons-Codec Library

We can use the Hex class supplied with the Apache commons-codec library:

public String encodeUsingApacheCommons(byte[] bytes) throws DecoderException { return Hex.encodeHexString(bytes); } public byte[] decodeUsingApacheCommons(String hexString) throws DecoderException { return Hex.decodeHex(hexString); }

The output of encodeHexString is always in lowercase.

7. Using Google's Guava Library

Let's take a look at how BaseEncoding class can be used for encoding and decoding byte array to the hexadecimal String:

public String encodeUsingGuava(byte[] bytes) { return BaseEncoding.base16().encode(bytes); } public byte[] decodeUsingGuava(String hexString) { return BaseEncoding.base16() .decode(hexString.toUpperCase()); } 

The BaseEncoding encodes and decodes using uppercase characters by default. If we need to use lowercase characters, a new encoding instance should be created using static method lowercase.

8. Conclusion

En este artículo, aprendimos el algoritmo de conversión entre una matriz de bytes a una cadena hexadecimal . También discutimos varios métodos para codificar una matriz de bytes en una cadena hexadecimal y viceversa.

No se recomienda agregar una biblioteca para usar solo un par de métodos de utilidad. Por lo tanto, si no estamos usando las bibliotecas externas, deberíamos usar el algoritmo discutido. La clase DataTypeConverter es otra forma de codificar / decodificar entre varios tipos de datos.

Finalmente, el código fuente completo de este tutorial está disponible en GitHub.