Как преобразовать массив байтов в шестнадцатеричную строку в Java?
Правильное преобразование 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)создаёт копию массива, что может быть неэффективно.
Этот способ хорошо подходит для одноразовых преобразований или отладки, но не рекомендуется для высоконагруженных систем.