Conversion entre des tableaux d'octets et des chaînes hexadécimales en Java

1. Vue d'ensemble

Dans ce didacticiel, nous examinerons différentes façons de convertir un tableau d'octets en une chaîne hexadécimale , et vice versa.

Nous allons également comprendre le mécanisme de conversion et écrire notre implémentation pour y parvenir.

2. Conversion entre octet et hexadécimal

Tout d'abord, examinons la logique de conversion entre les nombres d'octets et hexadécimaux.

2.1. Octet à hexadécimal

Les octets sont des entiers signés 8 bits en Java. Par conséquent, nous devons convertir chaque segment de 4 bits en hexadécimal séparément et les concaténer . Par conséquent, nous obtiendrons deux caractères hexadécimaux après la conversion.

Par exemple, nous pouvons écrire 45 comme 0010 1101 en binaire, et l'équivalent hexadécimal sera «2d»:

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

Implémentons cette logique 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); }

Maintenant, comprenons le code ci-dessus en analysant chaque opération. Tout d'abord, nous avons créé un tableau de caractères de longueur 2 pour stocker la sortie:

char[] hexDigits = new char[2];

Ensuite, nous avons isolé les bits d'ordre supérieur en décalant vers la droite de 4 bits. Et puis, nous avons appliqué un masque pour isoler 4 bits d'ordre inférieur. Le masquage est nécessaire car les nombres négatifs sont représentés en interne comme un complément à deux du nombre positif:

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

Ensuite, nous convertissons les 4 bits restants en hexadécimal:

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

Enfin, nous créons un objet String à partir du tableau char. Et puis, renvoyé cet objet sous forme de tableau hexadécimal converti.

Maintenant, comprenons comment cela fonctionnera pour un octet négatif -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)

Il convient également de noter que le personnage. La méthode forDigit () renvoie toujours des caractères minuscules.

2.2. Hexadécimal en octet

Maintenant, convertissons un chiffre hexadécimal en octet. Comme nous le savons, un octet contient 8 bits. Par conséquent, nous avons besoin de deux chiffres hexadécimaux pour créer un octet .

Tout d'abord, nous allons convertir chaque chiffre hexadécimal en équivalent binaire séparément.

Et puis, nous devons concaténer les deux segments de quatre bits pour obtenir l'équivalent en octets:

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

Maintenant, écrivons l'opération 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; }

Comprenons cela, une opération à la fois.

Tout d'abord, nous avons converti les caractères hexadécimaux en nombres entiers:

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

Ensuite, nous avons décalé à gauche le chiffre le plus significatif de 4 bits. Par conséquent, la représentation binaire a des zéros à quatre bits les moins significatifs.

Ensuite, nous y avons ajouté le chiffre le moins significatif:

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

Maintenant, examinons de près la méthode toDigit () . Nous utilisons la méthode Character.digit () pour la conversion. Si la valeur de caractère transmise à cette méthode n'est pas un chiffre valide dans la base spécifiée, -1 est renvoyé.

Nous validons la valeur de retour et lançons une exception si une valeur non valide a été transmise.

3. Conversion entre tableaux d'octets et chaînes hexadécimales

À ce stade, nous savons comment convertir un octet en hexadécimal, et vice versa. Redimensionnons cet algorithme et convertissons le tableau d'octets en / à partir de la chaîne hexadécimale .

3.1. Tableau d'octets en chaîne hexadécimale

Nous devons parcourir le tableau et générer une paire hexadécimale pour chaque octet:

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(); }

Comme nous le savons déjà, la sortie sera toujours en minuscules.

3.2. Chaîne hexadécimale en tableau d'octets

Tout d'abord, nous devons vérifier si la longueur de la chaîne hexadécimale est un nombre pair. Cela est dû au fait qu'une chaîne hexadécimale avec une longueur impaire entraînera une représentation d'octet incorrecte.

Maintenant, nous allons parcourir le tableau et convertir chaque paire hexadécimale en octet:

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. Utilisation de la classe BigInteger

Nous pouvons créer un objet de type BigInteger en passant un tableau de signum et d'octets .

Maintenant, nous pouvons générer la chaîne hexadécimale à l'aide du format de méthode statique défini dans la 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

Dans cet article, nous avons appris l'algorithme de conversion entre un tableau d'octets en chaîne hexadécimale . Nous avons également discuté de diverses méthodes pour encoder un tableau d'octets en chaîne hexadécimale et vice versa.

Il n'est pas conseillé d'ajouter une bibliothèque pour utiliser uniquement quelques méthodes utilitaires. Par conséquent, si nous n'utilisons pas déjà les bibliothèques externes, nous devrions utiliser l'algorithme discuté. La classe DataTypeConverter est un autre moyen d'encoder / décoder entre différents types de données.

Enfin, le code source complet de ce tutoriel est disponible sur GitHub.