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

1. Vue d’ensemble

Dans ce didacticiel, nous allons examiner différentes manières de convertir un tableau d’octets en une chaîne hexadécimale String, et vice versa.

Nous comprendrons également le mécanisme de conversion et rédigerons notre implémentation pour y parvenir.

2. Conversion entre octet et hexadécimal

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

2.1. Octet en 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 hexagone séparément et les concaténer .

Par conséquent, nous aurons deux caractères hexadécimaux après la conversion.

Par exemple, nous pouvons écrire 45 sous la forme 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 à droite de 4 bits. Et ensuite, nous avons appliqué un masque pour isoler 4 bits d’ordre inférieur. Le masquage est obligatoire car les nombres négatifs sont représentés en interne par 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 de caractères. Et puis, renvoyé cet objet sous forme de tableau hexadécimal converti.

Voyons maintenant 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 la méthode _Character . forDigit () _ renvoie toujours des caractères minuscules.

2.2. Hexadécimal à Byte

Convertissons maintenant 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 convertirons chaque chiffre hexadécimal en équivalent binaire séparément.

Et ensuite, 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 entiers:

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

Ensuite, nous avons laissé le chiffre le plus significatif décalé de 4 bits. Par conséquent, la représentation binaire comporte des zéros au moins quatre bits de poids fort.

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

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

Examinons maintenant 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, la valeur -1 est renvoyée.

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 inversement. Modifions cet algorithme et convertissons un tableau d’octets en/à partir de hexadécimal String .

3.1. Byte Array to Hexadecimal String

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 minuscule.

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 String est un nombre pair. En effet, une chaîne hexadécimale de 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 String à l’aide du format de méthode statique défini dans String class:

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

Le format fourni générera une chaîne String. hexadécimale minuscule et minuscule. Nous pouvons également générer une chaîne majuscule en remplaçant «x» par «X».

Sinon, nous aurions pu utiliser la méthode toString () de BigInteger . La différence subtile d’utilisation de la méthode toString () est que la sortie n’est pas complétée par des zéros .

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

Examinons maintenant la conversion hexadécimale de chaînes String à byte :

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;
}
  • La méthode toByteArray () produit un bit de signe supplémentaire ** . Nous avons écrit un code spécifique pour gérer ce bit supplémentaire.

Par conséquent, nous devrions être conscients de ces détails avant d’utiliser la classe BigInteger pour la conversion.

5. Utilisation de la classe DataTypeConverter

La classe DataTypeConverter est fournie avec la bibliothèque JAXB. Cela fait partie de la bibliothèque standard jusqu’à Java 8. À partir de Java 9, nous devons ajouter explicitement le module java.xml.bind à l’exécution.

Jetons un coup d’œil à l’implémentation à l’aide de la classe DataTypeConverter :

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

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

Comme indiqué ci-dessus, il est très pratique d’utiliser DataTypeConverter class. La sortie de la méthode printHexBinary () est toujours en majuscule . Cette classe fournit un ensemble de méthodes d’impression et d’analyse pour la conversion de type de données.

Avant de choisir cette approche, nous devons nous assurer que la classe sera disponible au moment de l’exécution.

6. Utilisation de la bibliothèque Apache’s Commons-Codec

Nous pouvons utiliser la classe Hex fournie avec la bibliothèque Apache commons-codec:

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

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

La sortie de encodeHexString est toujours en minuscule .

7. Utilisation de la bibliothèque Google de goyave

Voyons comment la classe BaseEncoding peut être utilisée pour encoder et décoder un tableau à l’hexadécimal Chaîne:

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

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

8. Conclusion

Dans cet article, nous avons appris l’algorithme de conversion entre tableau d’octets en hexadécimal String . Nous avons également discuté de diverses méthodes pour coder 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 présenté. 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 didacticiel est https://github.com/eugenp/tutorials/tree/master/algorithms-miscellaneous-2 disponible sur GitHub].