Conversione tra array di byte e stringhe esadecimali in Java

1. Panoramica

In questo tutorial, daremo uno sguardo a diversi modi per convertire un array di byte in una stringa esadecimale e viceversa.

Capiremo anche il meccanismo di conversione e scriveremo la nostra implementazione per raggiungere questo obiettivo.

2. Conversione tra byte ed esadecimale

Prima di tutto, diamo un'occhiata alla logica di conversione tra byte e numeri esadecimali.

2.1. Byte in esadecimale

I byte sono interi con segno a 8 bit in Java. Pertanto, dobbiamo convertire ogni segmento a 4 bit in esadecimale separatamente e concatenarli . Di conseguenza, dopo la conversione otterremo due caratteri esadecimali.

Ad esempio, possiamo scrivere 45 come 0010 1101 in binario e l'equivalente esadecimale sarà "2d":

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

Implementiamo questa semplice logica in 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); }

Ora, comprendiamo il codice sopra analizzando ogni operazione. Prima di tutto, abbiamo creato un array di caratteri di lunghezza 2 per memorizzare l'output:

char[] hexDigits = new char[2];

Successivamente, abbiamo isolato i bit di ordine superiore spostando a destra di 4 bit. E poi, abbiamo applicato una maschera per isolare 4 bit di ordine inferiore. Il mascheramento è necessario perché i numeri negativi sono rappresentati internamente come complemento a due del numero positivo:

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

Quindi convertiamo i restanti 4 bit in esadecimali:

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

Infine, creiamo un oggetto String dall'array char. Quindi, ha restituito questo oggetto come matrice esadecimale convertita.

Ora, capiamo come funzionerà per 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)

Vale anche la pena notare che il personaggio. Il metodo forDigit () restituisce sempre caratteri minuscoli.

2.2. Da esadecimale a byte

Ora, convertiamo una cifra esadecimale in byte. Come sappiamo, un byte contiene 8 bit. Pertanto, abbiamo bisogno di due cifre esadecimali per creare un byte .

Prima di tutto, convertiremo ogni cifra esadecimale in equivalente binario separatamente.

Quindi, dobbiamo concatenare i due quattro segmenti di bit per ottenere l'equivalente in byte:

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

Ora scriviamo l'operazione in 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; }

Capiamo questo, un'operazione alla volta.

Prima di tutto, abbiamo convertito i caratteri esadecimali in numeri interi:

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

Quindi abbiamo lasciato la cifra più significativa spostata di 4 bit. Di conseguenza, la rappresentazione binaria ha zeri su quattro bit meno significativi.

Quindi, abbiamo aggiunto la cifra meno significativa:

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

Ora, esaminiamo da vicino il metodo toDigit () . Stiamo usando il metodo Character.digit () per la conversione. Se il valore del carattere passato a questo metodo non è una cifra valida nella radice specificata, viene restituito -1.

Stiamo convalidando il valore restituito e generando un'eccezione se è stato passato un valore non valido.

3. Conversione tra matrici di byte e stringhe esadecimali

A questo punto sappiamo come convertire un byte in esadecimale e viceversa. Ridimensioniamo questo algoritmo e convertiamo l'array di byte in / da una stringa esadecimale .

3.1. Da matrice di byte a stringa esadecimale

Dobbiamo scorrere l'array e generare una coppia esadecimale per ogni 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(); }

Come già sappiamo, l'output sarà sempre in minuscolo.

3.2. Da stringa esadecimale a matrice di byte

Prima di tutto, dobbiamo verificare se la lunghezza della stringa esadecimale è un numero pari. Questo perché una stringa esadecimale con lunghezza dispari risulterà in una rappresentazione in byte errata.

Ora itereremo l'array e convertiremo ogni coppia esadecimale in 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. Utilizzo della classe BigInteger

Possiamo creare un oggetto di tipo BigInteger passando un signum e un array di byte .

Ora, possiamo generare la stringa esadecimale con l'aiuto del formato del metodo statico definito nella classe 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

In questo articolo, abbiamo appreso l'algoritmo di conversione tra array di byte in String esadecimale . Abbiamo anche discusso vari metodi per codificare un array di byte in una stringa esadecimale e viceversa.

Non è consigliabile aggiungere una libreria per utilizzare solo un paio di metodi di utilità. Pertanto, se non stiamo già utilizzando le librerie esterne, dovremmo utilizzare l'algoritmo discusso. La classe DataTypeConverter è un altro modo per codificare / decodificare tra vari tipi di dati.

Infine, il codice sorgente completo di questo tutorial è disponibile su GitHub.