Преобразуване между байтови масиви и шестнадесетични низове в Java

1. Общ преглед

В този урок ще разгледаме различни начини за преобразуване на байтов масив в шестнадесетичен низ и обратно.

Също така ще разберем механизма за преобразуване и ще напишем нашата реализация, за да постигнем това.

2. Преобразуване между байт и шестнадесетичен

На първо място, нека да разгледаме логиката на преобразуване между байтови и шестнадесетични числа.

2.1. Байт до шестнадесетичен

Байтовете са 8-битови подписани цели числа в Java. Следователно трябва да конвертираме всеки 4-битов сегмент в шестнадесетичен поотделно и да ги обединим . Следователно ще получим два шестнадесетични знака след преобразуване.

Например, можем да запишем 45 като 0010 1101 в двоичен формат и шестнадесетичният еквивалент ще бъде „2d“:

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

Нека приложим тази проста логика в 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); }

Нека сега разберем горния код, като анализираме всяка операция. Първо, създадохме масив char с дължина 2, за да съхраняваме изхода:

char[] hexDigits = new char[2];

След това изолирахме битове от по-висок ред, като сменихме 4 бита вдясно. И тогава приложихме маска, за да изолираме 4 бита от по-нисък ред. Необходимо е маскиране, тъй като отрицателните числа са представени вътре като допълнение на две на положителното число:

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

След това преобразуваме останалите 4 бита в шестнадесетични:

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

И накрая, ние създаваме обект String от масива char. И след това върна този обект като преобразуван шестнадесетичен масив.

Сега нека разберем как това ще работи за отрицателен байт -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)

Също така си струва да се отбележи, че Характерът. Методът forDigit () винаги връща малки букви.

2.2. Шестнадесетичен на байт

Сега, нека преобразуваме шестнадесетична цифра в байт. Както знаем, байт съдържа 8 бита. Следователно са ни необходими две шестнадесетични цифри, за да създадем един байт .

На първо място, ще преобразуваме всяка шестнадесетична цифра в двоичен еквивалент поотделно.

И тогава, трябва да обединим двата четири битови сегмента, за да получим байтовия еквивалент:

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

Сега, нека напишем операцията в 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; }

Нека разберем това, една операция в даден момент.

На първо място, преобразувахме шестнадесетични знаци в цели числа:

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

След това оставихме изместената най-значима цифра с 4 бита. Следователно, двоичното представяне има нули в четири най-малко значими бита.

След това добавихме най-малко значимата цифра към него:

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

Сега, нека разгледаме внимателно метода toDigit () . За преобразуване използваме метода Character.digit () . Ако стойността на символа, предадена на този метод, не е валидна цифра в посочения радикс, се връща -1.

Потвърждаваме връщаната стойност и изхвърляме изключение, ако е предадена невалидна стойност.

3. Преобразуване между байтови масиви и шестнадесетични низове

На този етап знаем как да преобразуваме байт в шестнадесетичен и обратно. Нека мащабираме този алгоритъм и преобразуваме байтовия масив в / от шестнадесетичен низ .

3.1. Байтов масив до шестнадесетичен низ

Трябва да преминем през масива и да генерираме шестнадесетична двойка за всеки байт:

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

As we already know, the output will always be in lowercase.

3.2. Hexadecimal String to Byte Array

First of all, we need to check if the length of the hexadecimal String is an even number. This is because a hexadecimal String with odd length will result in incorrect byte representation.

Now, we'll iterate through the array and convert each hexadecimal pair to a 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. Using the BigInteger Class

We can create an object of type BigInteger by passing a signum and byte array.

Now, we can generate the hexadecimal String with the help of static method format defined in String class:

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

В тази статия научихме алгоритъма за преобразуване между байтов масив в шестнадесетичен низ . Също така обсъдихме различни методи за кодиране на байтов масив в шестнадесетичен низ и обратно.

Не се препоръчва да добавяте библиотека, за да използвате само няколко полезни метода. Следователно, ако вече не използваме външните библиотеки, трябва да използваме обсъдения алгоритъм. Класът DataTypeConverter е друг начин за кодиране / декодиране между различни типове данни.

И накрая, пълният изходен код на този урок е достъпен на GitHub.