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

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

 

1. обзор

В этом руководстве мы рассмотрим различные способы преобразования байтового массива в шестнадцатеричныйString, и наоборот.

Мы также поймем механизм преобразования и напишем нашу реализацию для достижения этой цели.

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

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

2.1. Байт в шестнадцатеричный

Байты - это 8-битные целые числа со знаком в Java. Следовательно, нам нужноconvert each 4-bit segment to hex separately and concatenate them. Следовательно, после преобразования мы получим два шестнадцатеричных символа.

Например, мы можем записать 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)

Также стоит отметить, что методCharacter.forDigit _ () _ всегда возвращает символы нижнего регистра.

2.2. Шестнадцатеричный в Байт

Теперь давайте преобразуем шестнадцатеричную цифру в байт. Как известно, байт содержит 8 бит. Следовательно,we need two hexadecimal digits to create one byte.

Прежде всего, мы отдельно преобразуем каждую шестнадцатеричную цифру в двоичный эквивалент.

И затем нам нужно объединить два четырехбитных сегмента, чтобы получить байтовый эквивалент:

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() для преобразования. If the character value passed to this method is not a valid digit in the specified radix, -1 is returned.

Мы проверяем возвращаемое значение и генерируем исключение, если было передано недопустимое значение.

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

На данный момент мы знаем, как преобразовать байт в шестнадцатеричный, и наоборот. Давайте масштабируем этот алгоритм и преобразуем массив байтов в / из шестнадцатеричногоString.

3.1. Байтовый массив в шестнадцатеричныйString

Нам нужно перебрать массив и сгенерировать шестнадцатеричную пару для каждого байта:

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. Шестнадцатеричная строка в массив байтов

Прежде всего, нам нужно проверить, является ли длина шестнадцатеричного числаString четным числом. Это потому, что шестнадцатеричныйString с нечетной длиной приведет к неправильному представлению байта.

Теперь мы переберем массив и преобразуем каждую шестнадцатеричную пару в байт:

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

Мы можемcreate an object of type BigInteger by passing a signum and byte array.

Теперь мы можем сгенерировать шестнадцатеричныйString с помощью формата статического метода, определенного в классеString:

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

Предоставленный формат будет генерировать заполненное нулями нижнее шестнадцатеричное числоString.. Мы также можем сгенерировать строку в верхнем регистре, заменив «x» на «X».

В качестве альтернативы мы могли бы использовать методtoString() изBigInteger. Тонкие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);
}

Теперь давайте посмотрим на преобразование массива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;
}

The toByteArray() method produces an additional sign bit. Мы написали специальный код для обработки этого дополнительного бита.

Следовательно, мы должны знать эти детали, прежде чем использовать класс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. output of the printHexBinary() method is always in uppercase. Этот класс предоставляет набор методов print и parse для преобразования типов данных.

Прежде чем выбрать этот подход, мы должны убедиться, что класс будет доступен во время выполнения.

6. Использование библиотеки кодеков Apache Commons

Мы можем использовать классHex, поставляемый с библиотекой общих кодеков Apache:

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

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

output of encodeHexString is always in lowercase.

7. Использование библиотеки Google Guava

Давайте посмотрим, как классBaseEncoding можно использовать для кодирования и декодирования массива байтов в шестнадцатеричный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. Если нам нужно использовать символы нижнего регистра, следует создать новый экземпляр кодировки с помощью статического методаlowercase.

8. Заключение

В этой статье мы узнали алгоритм преобразования байтового массива в шестнадцатеричныйString. Мы также обсудили различные методы кодирования байтового массива в шестнадцатеричную строку и наоборот.

Не рекомендуется добавлять библиотеку, чтобы использовать только несколько служебных методов. Поэтому, если мы еще не используем внешние библиотеки, мы должны использовать обсуждаемый алгоритм. КлассDataTypeConverter - это еще один способ кодирования / декодирования между различными типами данных.

Наконец, полный исходный код этого руководства -available on GitHub.