Conversione tra array di byte e stringhe esadecimali in Java
Panoramica
In questo tutorial, vedremo 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 ottenere questo risultato.
Conversione tra byte ed esadecimale
Prima di tutto, diamo uno sguardo alla logica di conversione tra byte e numeri esadecimali.
2.1. Da byte a esadecimale
I byte sono interi firmati a 8 bit in Java. Pertanto, abbiamo bisogno di convertire ogni segmento di 4 bit in esadecimale separatamente e concatenarli. Di conseguenza, otterremo due caratteri esadecimali dopo la conversione.
Per 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; hexDigits = Character.forDigit((num >> 4) & 0xF, 16); hexDigits = Character.forDigit((num & 0xF), 16); return new String(hexDigits);}
Ora, capiamo il codice sopra analizzando ogni operazione. Prima di tutto, abbiamo creato un array di char di lunghezza 2 per memorizzare l’output:
char hexDigits = new char;
In seguito, abbiamo isolato i bit di ordine superiore spostando a destra 4 bit. E poi, abbiamo applicato una maschera per isolare i 4 bit di ordine inferiore. La mascheratura è necessaria perché i numeri negativi sono rappresentati internamente come complemento a due del numero positivo:
hexDigits = Character.forDigit((num >> 4) & 0xF, 16);
Poi convertiamo i 4 bit rimanenti in esadecimale:
hexDigits = Character.forDigit((num & 0xF), 16);
Finalmente, creiamo un oggetto String dall’array char. E poi, restituiamo questo oggetto come array esadecimale convertito.
Ora, cerchiamo di capire come questo funzionerà per un byte negativo -4:
hexDigits:1111 1100 >> 4 = 1111 1111 1111 1111 1111 1111 1111 11111111 1111 1111 1111 1111 1111 1111 1111 & 0xF = 0000 0000 0000 0000 0000 0000 0000 1111 = 0xfhexDigits:1111 1100 & 0xF = 0000 1100 = 0xcTherefore: -4 (base 10) = 1111 1100 (base 2) = fc (base 16)
E’ anche da notare che il metodo Character.forDigit() restituisce sempre caratteri minuscoli.
2.2. Da esadecimale a byte
Adesso, 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.
E poi, dobbiamo concatenare i due quattro segmenti di bit per ottenere il byte equivalente:
Hexadecimal: 2d2 = 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 interi:
int firstDigit = toDigit(hexString.charAt(0));int secondDigit = toDigit(hexString.charAt(1));
Poi abbiamo spostato la cifra più significativa di 4 bit. Di conseguenza, la rappresentazione binaria ha degli zeri ai quattro bit meno significativi.
Poi, abbiamo aggiunto la cifra meno significativa:
return (byte) ((firstDigit << 4) + secondDigit);
Ora, esaminiamo il metodo toDigit() da vicino. Stiamo usando il metodo Character.digit() per la conversione. Se il valore del carattere passato a questo metodo non è una cifra valida nel radix specificato, viene restituito -1.
Convalidiamo il valore di ritorno e lanciamo un’eccezione se viene passato un valore non valido.
Conversione tra array di byte e stringhe esadecimali
A questo punto, sappiamo come convertire un byte in esadecimale e viceversa. Scaliamo questo algoritmo e convertiamo l’array di byte in/dalla stringa esadecimale.
3.1. Byte Array to Hexadecimal String
Abbiamo bisogno di eseguire un ciclo attraverso 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)); } return hexStringBuffer.toString();}
Come già sappiamo, l’output sarà sempre in minuscolo.
3.2. Hexadecimal String to Byte Array
Prima di tutto, dobbiamo controllare se la lunghezza della stringa esadecimale è un numero pari. Questo perché una stringa esadecimale di lunghezza dispari risulterà in una rappresentazione errata del byte.
Ora, itereremo attraverso 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; for (int i = 0; i < hexString.length(); i += 2) { bytes = hexToByte(hexString.substring(i, i + 2)); } return bytes;}
Utilizzando la 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 metodo statico format definito nella classe String:
public String encodeUsingBigIntegerStringFormat(byte bytes) { BigInteger bigInteger = new BigInteger(1, bytes); return String.format( "%0" + (bytes.length << 1) + "x", bigInteger);}
Il format fornito genererà una stringa esadecimale minuscola con zero punti. Possiamo anche generare una stringa maiuscola sostituendo “x” con “X”.
In alternativa, avremmo potuto usare il metodo toString() di BigInteger. La sottile differenza nell’usare il metodo toString() è che l’output non è imbottito di zeri iniziali:
public String encodeUsingBigIntegerToString(byte bytes) { BigInteger bigInteger = new BigInteger(1, bytes); return bigInteger.toString(16);}
Ora, diamo un’occhiata alla conversione da stringa esadecimale a byte Array:
public byte decodeUsingBigInteger(String hexString) { byte byteArray = new BigInteger(hexString, 16) .toByteArray(); if (byteArray == 0) { byte output = new byte; System.arraycopy( byteArray, 1, output, 0, output.length); return output; } return byteArray;}
Il metodo toByteArray() produce un ulteriore bit di segno. Abbiamo scritto del codice specifico per gestire questo bit aggiuntivo.
Pertanto, dovremmo essere consapevoli di questi dettagli prima di usare la classe BigInteger per la conversione.
Usare la classe DataTypeConverter
La classe DataTypeConverter è fornita con la libreria JAXB. Fa parte della libreria standard fino a Java 8. A partire da Java 9, dobbiamo aggiungere esplicitamente il modulo java.xml.bind al runtime.
Diamo un’occhiata all’implementazione usando la classe DataTypeConverter:
public String encodeUsingDataTypeConverter(byte bytes) { return DatatypeConverter.printHexBinary(bytes);}public byte decodeUsingDataTypeConverter(String hexString) { return DatatypeConverter.parseHexBinary(hexString);}
Come mostrato sopra, è molto conveniente usare la classe DataTypeConverter. L’output del metodo printHexBinary() è sempre in maiuscolo. Questa classe fornisce un insieme di metodi print e parse per la conversione del tipo di dati.
Prima di scegliere questo approccio, dobbiamo assicurarci che la classe sia disponibile a runtime.
Utilizzando la libreria Commons-Codec di Apache
Possiamo usare la classe Hex fornita con la libreria commons-codec di Apache:
public String encodeUsingApacheCommons(byte bytes) throws DecoderException { return Hex.encodeHexString(bytes);}public byte decodeUsingApacheCommons(String hexString) throws DecoderException { return Hex.decodeHex(hexString);}
L’output di encodeHexString è sempre in minuscolo.
Usando la libreria Guava di Google
Diamo un’occhiata a come la classe BaseEncoding può essere usata per codificare e decodificare l’array di byte nella stringa esadecimale:
public String encodeUsingGuava(byte bytes) { return BaseEncoding.base16().encode(bytes);}public byte decodeUsingGuava(String hexString) { return BaseEncoding.base16() .decode(hexString.toUpperCase());}
La BaseEncoding codifica e decodifica usando caratteri maiuscoli per default. Se abbiamo bisogno di usare caratteri minuscoli, una nuova istanza di codifica dovrebbe essere creata usando il metodo statico lowercase.
Conclusione
In questo articolo, abbiamo imparato l’algoritmo di conversione tra array di byte e stringa esadecimale. Abbiamo anche discusso vari metodi per codificare array di byte in stringa esadecimale e viceversa.
Non è consigliabile aggiungere una libreria per usare solo un paio di metodi di utilità. Pertanto, se non stiamo già usando le librerie esterne, dovremmo usare l’algoritmo discusso. La classe DataTypeConverter è un altro modo per codificare/decodificare tra vari tipi di dati.