Как я могу преобразовать массив байтов в шестнадцатеричный в Java?
У меня есть массив байтов. Я хочу, чтобы каждая строка байтов этого массива была преобразована в соответствующее шестнадцатеричное значение.
Есть ли в Java какая-либо функция для преобразования массива байтов в шестнадцатеричный?
Переведено автоматически
Ответ 1
byte[] bytes = {-1, 0, 1, 2, 3 };
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
System.out.println(sb.toString());
// prints "FF 00 01 02 03 "
Смотрите также
java.util.Formatter
синтаксис%[flags][width]conversion
- Флаг
'0'
- результат будет дополнен нулем - Ширина
2
- Преобразование
'X'
- Результат форматируется как шестнадцатеричное целое число в верхнем регистре
- Флаг
Глядя на текст вопроса, также возможно, что это то, что запрашивается:
String[] arr = {"-1", "0", "10", "20" };
for (int i = 0; i < arr.length; i++) {
arr[i] = String.format("%02x", Byte.parseByte(arr[i]));
}
System.out.println(java.util.Arrays.toString(arr));
// prints "[ff, 00, 0a, 14]"
Здесь используется несколько ответов Integer.toHexString(int)
; это выполнимо, но с некоторыми оговорками. Поскольку параметром является int
, в byte
аргумент выполняется расширяющее примитивное преобразование, которое включает расширение знака.
byte b = -1;
System.out.println(Integer.toHexString(b));
// prints "ffffffff"
8-разрядный byte
, который подписан в Java, расширен до 32-разрядного int
. Чтобы эффективно отменить это расширение знака, можно замаскировать byte
с помощью 0xFF
.
byte b = -1;
System.out.println(Integer.toHexString(b & 0xFF));
// prints "ff"
Еще одна проблема с использованием toHexString
заключается в том, что он не заполняется нулями:
byte b = 10;
System.out.println(Integer.toHexString(b & 0xFF));
// prints "a"
Оба фактора в совокупности должны сделать String.format
решение более предпочтительным.
Ссылки
- JLS 4.2.1 Интегральные типы и значения
- Для
byte
, от-128
до127
включительно
- Для
- JLS 5.1.2 Расширяющее преобразование примитивов
Ответ 2
Я публикую, потому что ни один из существующих ответов не объясняет, почему их подходы работают, что, я думаю, действительно важно для этой проблемы. В некоторых случаях это приводит к тому, что предлагаемое решение кажется излишне сложным и утонченным. Для иллюстрации я приведу довольно простой подход, но я приведу немного больше деталей, чтобы помочь проиллюстрировать, почему это работает.
Прежде всего, что мы пытаемся сделать? Мы хотим преобразовать значение в байтах (или массив байтов) в строку, которая представляет шестнадцатеричное значение в ASCII. Итак, шаг первый - точно выяснить, что такое байт в Java:
Тип данных byte - это 8-разрядное целое число со знаком, дополняющее двойку. Оно имеет минимальное значение -128 и максимальное значение 127 (включительно). Тип данных byte может быть полезен для экономии памяти в больших массивах, где экономия памяти действительно имеет значение. Они также могут использоваться вместо int, где их ограничения помогают прояснить ваш код; тот факт, что диапазон переменной ограничен, может служить формой документации.
Что это значит? Несколько вещей: Во-первых, и это самое главное, это означает, что мы работаем с 8 битами. Так, например, мы можем записать число 2 как 0000 0010. Однако, поскольку это дополнение two, мы записываем отрицательное значение 2 следующим образом: 1111 1110. Это также означает, что преобразование в шестнадцатеричный формат очень простое. То есть вы просто преобразуете каждый 4-битный сегмент непосредственно в шестнадцатеричный. Обратите внимание, что для понимания смысла отрицательных чисел в этой схеме вам сначала нужно понять дополнение two . Если вы еще не понимаете дополнение two, вы можете прочитать отличное объяснение здесь: http://www.cs.cornell.edu / ~tomf/notes/cps104/twoscomp.html
Преобразование дополнения Two в шестнадцатеричный в общих чертах
Как только число дополняется двумя, преобразовать его в шестнадцатеричный очень просто. В общем, преобразование из двоичного файла в шестнадцатеричный очень простое, и, как вы увидите в следующих двух примерах, вы можете перейти непосредственно от дополнения two к шестнадцатеричному.
Примеры
Пример 1: Преобразовать 2 в шестнадцатеричный.
1) Сначала преобразуйте 2 в двоичный в дополнении two:
2 (base 10) = 0000 0010 (base 2)
2) Теперь преобразуйте двоичный код в шестнадцатеричный:
0000 = 0x0 in hex
0010 = 0x2 in hex
therefore 2 = 0000 0010 = 0x02.
Пример 2: Преобразовать -2 (в дополнение к двум) в шестнадцатеричный.
1) Сначала преобразуйте -2 в двоичный файл в дополнении two:
-2 (base 10) = 0000 0010 (direct conversion to binary)
1111 1101 (invert bits)
1111 1110 (add 1)
therefore: -2 = 1111 1110 (in two's complement)
2) Теперь преобразуем в шестнадцатеричный:
1111 = 0xF in hex
1110 = 0xE in hex
therefore: -2 = 1111 1110 = 0xFE.
Как это сделать в Java
Теперь, когда мы рассмотрели концепцию, вы обнаружите, что мы можем достичь желаемого с помощью простой маскировки и сдвига. Главное, что нужно понимать, это то, что байт, который вы пытаетесь преобразовать, уже находится в дополнении two. Вы не выполняете это преобразование самостоятельно. Я думаю, что это основная путаница в этом вопросе. Возьмем, к примеру, следующий массив байтов:
byte[] bytes = new byte[]{-2,2};
Мы просто вручную преобразовали их в шестнадцатеричный, как указано выше, но как мы можем сделать это в Java? Вот как:
Шаг 1: Создаем StringBuffer для хранения наших вычислений.
StringBuffer buffer = new StringBuffer();
Шаг 2: Выделите биты более высокого порядка, преобразуйте их в шестнадцатеричный и добавьте их в буфер
Учитывая двоичное число 1111 1110, мы можем выделить биты более высокого порядка, сначала сдвинув их на 4, а затем обнулив остальную часть числа. Логически это просто, однако детали реализации в Java (и многих языках) создают проблему из-за расширения знака. По сути, когда вы сдвигаете значение в байтах, Java сначала преобразует ваше значение в целое число, а затем выполняет расширение знака. Итак, хотя вы ожидаете, что 1111 1110 >> 4 будет 0000 1111, на самом деле в Java он представлен как дополнение двух 0xFFFFFFFF!
Итак, возвращаясь к нашему примеру:
1111 1110 >> 4 (shift right 4) = 1111 1111 1111 1111 1111 1111 1111 1111 (32 bit sign-extended number in two's complement)
Затем мы можем изолировать биты с помощью маски:
1111 1111 1111 1111 1111 1111 1111 1111 & 0xF = 0000 0000 0000 0000 0000 0000 0000 1111
therefore: 1111 = 0xF in hex.
В Java мы можем сделать все это одним выстрелом:
Character.forDigit((bytes[0] >> 4) & 0xF, 16);
Функция forDigit просто сопоставляет число, которое вы передаете, с набором шестнадцатеричных чисел 0-F.
Шаг 3: Далее нам нужно изолировать биты младшего порядка. Поскольку нужные нам биты уже находятся в правильном положении, мы можем просто замаскировать их:
1111 1110 & 0xF = 0000 0000 0000 0000 0000 0000 0000 1110 (recall sign extension from before)
therefore: 1110 = 0xE in hex.
Как и раньше, в Java мы можем сделать все это одним выстрелом:
Character.forDigit((bytes[0] & 0xF), 16);
Собрав все это вместе, мы можем сделать это как цикл for и преобразовать весь массив целиком:
for(int i=0; i < bytes.length; i++){
buffer.append(Character.forDigit((bytes[i] >> 4) & 0xF, 16));
buffer.append(Character.forDigit((bytes[i] & 0xF), 16));
}
Надеюсь, это объяснение прояснит ситуацию для тех из вас, кому интересно, что именно происходит во многих примерах, которые вы найдете в Интернете. Надеюсь, я не допустил каких-либо вопиющих ошибок, но предложения и исправления очень приветствуются!
Ответ 3
Самый быстрый способ, который я пока нашел для этого, следующий:
private static final String HEXES = "0123456789ABCDEF";
static String getHex(byte[] raw) {
final StringBuilder hex = new StringBuilder(2 * raw.length);
for (final byte b : raw) {
hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
}
return hex.toString();
}
Это в ~ 50 раз быстрее, чем String.format
. если вы хотите протестировать это:
public class MyTest{
private static final String HEXES = "0123456789ABCDEF";
@Test
public void test_get_hex() {
byte[] raw = {
(byte) 0xd0, (byte) 0x0b, (byte) 0x01, (byte) 0x2a, (byte) 0x63,
(byte) 0x78, (byte) 0x01, (byte) 0x2e, (byte) 0xe3, (byte) 0x6c,
(byte) 0xd2, (byte) 0xb0, (byte) 0x78, (byte) 0x51, (byte) 0x73,
(byte) 0x34, (byte) 0xaf, (byte) 0xbb, (byte) 0xa0, (byte) 0x9f,
(byte) 0xc3, (byte) 0xa9, (byte) 0x00, (byte) 0x1e, (byte) 0xd5,
(byte) 0x4b, (byte) 0x89, (byte) 0xa3, (byte) 0x45, (byte) 0x35,
(byte) 0xd6, (byte) 0x10,
};
int N = 77777;
long t;
{
t = System.currentTimeMillis();
for (int i = 0; i < N; i++) {
final StringBuilder hex = new StringBuilder(2 * raw.length);
for (final byte b : raw) {
hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
}
hex.toString();
}
System.out.println(System.currentTimeMillis() - t); // 50
}
{
t = System.currentTimeMillis();
for (int i = 0; i < N; i++) {
StringBuilder hex = new StringBuilder(2 * raw.length);
for (byte b : raw) {
hex.append(String.format("%02X", b));
}
hex.toString();
}
System.out.println(System.currentTimeMillis() - t); // 2535
}
}
}
Редактировать: только что нашел кое-что немного быстрее, и это удерживается на одной строке, но не совместимо с JRE 9. Используйте на свой страх и риск
import javax.xml.bind.DatatypeConverter;
DatatypeConverter.printHexBinary(raw);
Ответ 4
Попробуйте этот способ:
byte bv = 10;
String hexString = Integer.toHexString(bv);
Работа с массивом (если я вас правильно понял):
byte[] bytes = {9, 10, 11, 15, 16};
StringBuffer result = new StringBuffer();
for (byte b : bytes) {
result.append(String.format("%02X ", b));
result.append(" "); // delimiter
}
return result.toString();
Как упоминали полигенные поставщики, String.format()
это правильный ответ по сравнению с Integer.toHexString()
(поскольку он правильно обрабатывает отрицательные числа).