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);
}
Теперь давайте разберемся в приведенном выше коде, проанализировав каждую операцию. Прежде всего, мы создали массив символов длины 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)
Также стоит отметить, что Character.
Метод 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();
}
Как мы уже знаем, вывод всегда будет в нижнем регистре.
3.2. Шестнадцатеричная строка в массив байтов
Прежде всего, нам нужно проверить, является ли длина шестнадцатеричной строки
четным числом. Это связано с тем, что шестнадцатеричная строка
нечетной длины приведет к неправильному представлению байтов.
Теперь мы пройдемся по массиву и преобразуем каждую шестнадцатеричную пару в байт:
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. Использование класса BigInteger
Мы можем создать объект типа BigInteger
, передав знак и массив байтов .
Теперь мы можем сгенерировать шестнадцатеричную строку
с помощью формата статического метода, определенного в классе String :
public String encodeUsingBigIntegerStringFormat(byte[] bytes) {
BigInteger bigInteger = new BigInteger(1, bytes);
return String.format(
"%0" + (bytes.length << 1) + "x", bigInteger);
}
Предоставленный формат будет генерировать заполненную нулями строчную шестнадцатеричную строку.
Мы также можем сгенерировать строку в верхнем регистре, заменив «x» на «X».
В качестве альтернативы мы могли бы использовать метод toString()
из BigInteger
. Тонкое отличие использования метода toString()
заключается в том, что вывод не дополняется ведущими нулями :
public String encodeUsingBigIntegerToString(byte[] bytes) {
BigInteger bigInteger = new BigInteger(1, bytes);
return bigInteger.toString(16);
}
Теперь давайте посмотрим на преобразование шестнадцатеричной строки
в байтовый
массив:
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;
}
Метод toByteArray()
создает дополнительный знаковый бит . Мы написали специальный код для обработки этого дополнительного бита.
Следовательно, мы должны знать об этих деталях, прежде чем использовать класс BigInteger
для преобразования.
5. Использование класса DataTypeConverter
Класс DataTypeConverter
поставляется с библиотекой JAXB. Это часть стандартной библиотеки до Java 8. Начиная с Java 9, нам нужно явно добавить модуль java.xml.bind
в среду выполнения.
Давайте посмотрим на реализацию с использованием класса DataTypeConverter
:
public String encodeUsingDataTypeConverter(byte[] bytes) {
return DatatypeConverter.printHexBinary(bytes);
}
public byte[] decodeUsingDataTypeConverter(String hexString) {
return DatatypeConverter.parseHexBinary(hexString);
}
Как показано выше, очень удобно использовать класс DataTypeConverter
. Вывод метода printHexBinary()
всегда в верхнем регистре . Этот класс предоставляет набор методов печати и синтаксического анализа для преобразования типа данных.
Прежде чем выбрать этот подход, нам нужно убедиться, что класс будет доступен во время выполнения.
6. Использование библиотеки Apache Commons-Codec
Мы можем использовать класс Hex
, поставляемый с библиотекой 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);
}
Вывод encodeHexString всегда в нижнем регистре
.
7. Использование библиотеки Google Guava
Давайте посмотрим, как можно использовать класс BaseEncoding
для кодирования и декодирования массива байтов в шестнадцатеричную строку:
public String encodeUsingGuava(byte[] bytes) {
return BaseEncoding.base16().encode(bytes);
}
public byte[] decodeUsingGuava(String hexString) {
return BaseEncoding.base16()
.decode(hexString.toUpperCase());
}
BaseEncoding по умолчанию
кодирует и декодирует символы верхнего регистра . Если нам нужно использовать символы нижнего регистра, новый экземпляр кодировки должен быть создан с использованием статического метода нижнего регистра .
8. Заключение
В этой статье мы изучили алгоритм преобразования массива байтов в шестнадцатеричную строку
. Мы также обсудили различные методы кодирования байтового массива в шестнадцатеричную строку и наоборот.
Не рекомендуется добавлять библиотеку, чтобы использовать только пару служебных методов. Поэтому, если мы еще не используем внешние библиотеки, мы должны использовать обсуждаемый алгоритм. Класс DataTypeConverter
— это еще один способ кодирования/декодирования между различными типами данных.
Наконец, полный исходный код этого руководства доступен на GitHub .