Avatar ·

Как преобразовать массив байтов в шестнадцатеричную строку в Java?

Всем привет! Я новичок в Java и столкнулся с такой задачей: мне нужно преобразовать массив байтов (byte[]) в строку, представляющую собой шестнадцатеричное (hex) представление этих байтов. Например, если у меня есть массив byte[] {0x0A, 0xFF, 0x1B}, я хочу получить строку "0AFF1B". Проблема в том, что при попытке просто создать строку из массива байт с помощью new String(bytes), я получаю кракозябры, а не hex-представление. Я понимаю, что это неправильно, так как String ожидает символы в кодировке, а не произвольные байты. Я написал простой код, который пытается сделать это вручную: ```java public static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(Integer.toHexString(b & 0xFF)); } return sb.toString(); } ``` Но он работает неправильно: для байтов со значением от 0 до 15 (например, 0x0A) он выводит только один символ ("A"), а не два ("0A"). Кроме того, если байт отрицательный (например, -1, что соответствует 0xFF), результат выглядит странно. Подскажите, как правильно реализовать такое преобразование, чтобы оно работало для всех возможных значений байтов (0-255) и всегда выдавало два шестнадцатеричных символа на каждый байт? Буду благодарен за подробное объяснение и рабочий код. <<>>

Правильное преобразование byte[] в hex-строку

Ваша интуиция использовать Integer.toHexString() верна, но есть несколько нюансов, которые нужно учесть. Основные проблемы вашего кода:

  • Для значений от 0 до 15 (0x0-0xF) метод возвращает один символ, а не два.
  • Для отрицательных байтов (старший бит установлен) требуется корректная маскировка.

Решение №1: Использование StringBuilder с форматированием

Самый надёжный способ — использовать String.format() или Formatter для гарантированного вывода двух символов:

public static String bytesToHex(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (byte b : bytes) {
        // & 0xFF убирает знаковое расширение для отрицательных байтов
        // %02x — формат: два шестнадцатеричных символа, с ведущим нулём
        sb.append(String.format("%02x", b & 0xFF));
    }
    return sb.toString();
}

Объяснение:

  • b & 0xFF — побитовое И с 0xFF (255) преобразует отрицательный байт в положительное целое число от 0 до 255. Например, (byte) -1 & 0xFF = 255.
  • %02x — спецификатор формата: 0 — дополнять нулями слева, 2 — минимальная ширина в 2 символа, x — шестнадцатеричное число в нижнем регистре.

Решение №2: Использование javax.xml.bind.DatatypeConverter (Java 8 и старше)

Если вы используете Java 8 или более раннюю версию, есть встроенный класс:

import javax.xml.bind.DatatypeConverter;

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

Этот метод возвращает строку в верхнем регистре (например, "0AFF1B").

Решение №3: Использование java.util.HexFormat (Java 17+)

Начиная с Java 17, появился официальный способ:

public static String bytesToHex(byte[] bytes) {
    return HexFormat.of().formatHex(bytes);
}

По умолчанию возвращает строку в нижнем регистре. Можно настроить:

HexFormat.of().withUpperCase().formatHex(bytes); // верхний регистр

Решение №4: Оптимизированная ручная реализация

Если вам нужно максимальное быстродействие (например, для больших массивов):

private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();

public static String bytesToHex(byte[] bytes) {
    char[] hexChars = new char[bytes.length * 2];
    for (int i = 0; i < bytes.length; i++) {
        int v = bytes[i] & 0xFF;
        hexChars[i * 2] = HEX_ARRAY[v >>> 4];
        hexChars[i * 2 + 1] = HEX_ARRAY[v & 0x0F];
    }
    return new String(hexChars);
}

Как это работает:

  • v >>> 4 — сдвиг вправо на 4 бита, получаем старший полубайт (0-15).
  • v & 0x0F — маскировка, получаем младший полубайт.
  • Используем предварительно заполненный массив символов для конвертации цифры в символ.

Тестирование

byte[] test = {0x0A, (byte) 0xFF, 0x1B, 0x00, 0x7F};
System.out.println(bytesToHex(test)); // "0aff1b007f" (нижний регистр)

Рекомендация

Для современных проектов (Java 17+) используйте HexFormat. Для более старых версий — DatatypeConverter или оптимизированную ручную реализацию. Избегайте String.format() внутри цикла для больших массивов из-за накладных расходов на создание множества промежуточных строк.

<<>>

Альтернативный подход: Использование BigInteger

Если ваш массив байт небольшой (до 16-32 байт), можно использовать BigInteger для преобразования:

public static String bytesToHex(byte[] bytes) {
    // Создаём BigInteger из массива байт (с учётом знака)
    BigInteger bigInt = new BigInteger(1, bytes);
    // Преобразуем в hex-строку
    String hex = bigInt.toString(16);
    // Дополняем нулями слева, если нужно
    int expectedLength = bytes.length * 2;
    while (hex.length() < expectedLength) {
        hex = "0" + hex;
    }
    return hex;
}

Плюсы:

  • Короткий и читаемый код.
  • Не требует ручной обработки каждого байта.

Минусы:

  • Медленнее для больших массивов (BigInteger создаёт объект).
  • Требуется ручное дополнение нулями, если ведущие байты равны нулю.
  • Конструктор BigInteger(1, bytes) создаёт копию массива, что может быть неэффективно.

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