#java #types #endianness
#java #типы #порядковый номер
Вопрос:
Я хочу сохранить некоторые данные в байтовых массивах в Java. В основном просто числа, которые могут занимать до 2 байт на число.
Я хотел бы знать, как я могу преобразовать целое число в массив байтов длиной 2 байта и наоборот. Я нашел много решений в Google, но большинство из них не объясняют, что происходит в коде. Есть много изменяющихся вещей, которые я не совсем понимаю, поэтому я был бы признателен за базовое объяснение.
Комментарии:
1. Насколько вы понимаете сдвиг битов? Похоже, вопрос действительно «что делает сдвиг битов» больше, чем о преобразовании в байтовые массивы, на самом деле — если вы действительно хотите понять, как будет работать преобразование.
2. (Просто чтобы уточнить, я согласен с любым вопросом, но стоит уточнить, на какой вопрос вы действительно хотите получить ответ. Скорее всего, вы получите ответ, который будет более полезен для вас таким образом.)
3. Хорошо, я понял вашу точку зрения! Спасибо за замечание. Я знаю, что такое сдвиг битов, я просто еще не понял, для чего он используется при преобразовании байтовых массивов.
4. @prekageo и Джефф Меркадо Спасибо за ваши два ответа. prekageo дал хорошее объяснение того, как это делается, хорошая ссылка! Это делает его намного понятнее для меня. И решение Джеффа Меркадоса решило проблему, с которой я столкнулся.
Ответ №1:
Используйте классы, найденные в java.nio
пространстве имен, в частности, ByteBuffer
. Он может сделать всю работу за вас.
byte[] arr = { 0x00, 0x01 };
ByteBuffer wrapped = ByteBuffer.wrap(arr); // big-endian by default
short num = wrapped.getShort(); // 1
ByteBuffer dbuf = ByteBuffer.allocate(2);
dbuf.putShort(num);
byte[] bytes = dbuf.array(); // { 0, 1 }
Комментарии:
1. Это слишком дорого, если массив байтов содержит только 1 или 2 целых числа? Не уверен в стоимости построения a
ByteBuffer
.2. Как часто вы работаете с двоичными данными в 2-4 байтовых фрагментах? Действительно? Разумная реализация будет либо работать с ним в буферных блоках (обычно 4 КБ), либо использовать другие библиотеки ввода-вывода, которые скрывают эту деталь. В рамках фреймворка есть целая библиотека, предназначенная для помощи вам в работе с буферами данных. Вы оказываете медвежью услугу себе и другим разработчикам вашего кода, когда выполняете обычные операции без уважительной причины (будь то perf или другая критическая операция). Эти буферы являются просто оболочками, которые работают с массивами, не более того.
3. Как получилось, что вы можете создать экземпляр абстрактного класса?
4. @JaveneCPPMcGowan В этом ответе нет прямого экземпляра. Если вы имеете в виду фабричные методы
wrap
иallocate
, они не возвращают экземпляр абстрактного классаByteBuffer
.5. Не решение для шага в 3 байта. Мы можем получить
Char
,Short
,Int
. Я полагаю, я мог бы заполнить до 4 байтов и отбрасывать 4-й каждый раз, но я бы предпочел этого не делать.
Ответ №2:
byte[] toByteArray(int value) {
return ByteBuffer.allocate(4).putInt(value).array();
}
byte[] toByteArray(int value) {
return new byte[] {
(byte)(value >> 24),
(byte)(value >> 16),
(byte)(value >> 8),
(byte)value };
}
int fromByteArray(byte[] bytes) {
return ByteBuffer.wrap(bytes).getInt();
}
// packing an array of 4 bytes to an int, big endian, minimal parentheses
// operator precedence: <<, amp;, |
// when operators of equal precedence (here bitwise OR) appear in the same expression, they are evaluated from left to right
int fromByteArray(byte[] bytes) {
return bytes[0] << 24 | (bytes[1] amp; 0xFF) << 16 | (bytes[2] amp; 0xFF) << 8 | (bytes[3] amp; 0xFF);
}
// packing an array of 4 bytes to an int, big endian, clean code
int fromByteArray(byte[] bytes) {
return ((bytes[0] amp; 0xFF) << 24) |
((bytes[1] amp; 0xFF) << 16) |
((bytes[2] amp; 0xFF) << 8 ) |
((bytes[3] amp; 0xFF) << 0 );
}
При упаковке байтов со знаком в int каждый байт должен быть замаскирован, поскольку он расширяется до 32 бит (а не расширяется до нуля) из-за правила арифметического продвижения (описано в JLS, Conversations and Promotions ).
С этим связана интересная головоломка, описанная в Java Puzzlers («Большое удовольствие от каждого байта») Джошуа Блоха и Нила Гафтера . При сравнении байтового значения со значением int байт расширяется по знаку до int, а затем это значение сравнивается с другим int
byte[] bytes = (…)
if (bytes[0] == 0xFF) {
// dead code, bytes[0] is in the range [-128,127] and thus never equal to 255
}
Обратите внимание, что все числовые типы подписаны в Java, за исключением того, что char является 16-разрядным целочисленным типом без знака.
Комментарии:
1. Я думаю
amp; 0xFF
, что s не нужны.2. @LeifEricson Я считаю
amp; 0xFF
, что s необходимы, поскольку он сообщает JVM преобразовать байт со знаком в целое число только с этими установленными битами. В противном случае байт -1 (0xFF) превратится в int -1 (0xFFFFFFFF). Я могу ошибаться, и даже если это так, это не повредит и прояснит ситуацию.3. amp; 0xFF действительно является обязательным.
byte b = 0; b |= 0x88; System.out.println(Integer.toString(b, 16)); //Output: -78 System.out.println(Integer.toString(b amp; 0xFF, 16)); //Output: 88
4. @ptntialunrlsd На самом деле нет. Перед выполнением операции amp;
byte
с помощью функции 0xFF (int
) JVM сначала преобразует значениеbyte
toint
с расширением 1 или расширением 0 в соответствии с начальным битом. В Java нет байтов без знака,byte
s всегда подписаны.5. При разборе int из массива байтов обратите внимание на размер массива байтов, если он превышает 4 байта, согласно документу
ByteBuffer.getInt()
:Reads the next four bytes at this buffer's current position
, будут проанализированы только первые 4 байта, что не должно быть тем, что вы хотите.
Ответ №3:
Вы также можете использовать BigInteger для байтов переменной длины. Вы можете преобразовать его в long, int или short, в зависимости от того, что вам подходит.
new BigInteger(bytes).intValue();
или для обозначения полярности:
new BigInteger(1, bytes).intValue();
Чтобы получить байты обратно, просто:
new BigInteger(bytes).toByteArray()
Несмотря на простоту, я просто хотел указать, что если вы запускаете это много раз в цикле, это может привести к большой сборке мусора. Это может быть проблемой в зависимости от вашего варианта использования.
Комментарии:
1. Обратите внимание, что начиная с 1.8, это
intValueExact
, неintValue
Ответ №4:
Базовая реализация будет примерно такой:
public class Test {
public static void main(String[] args) {
int[] input = new int[] { 0x1234, 0x5678, 0x9abc };
byte[] output = new byte[input.length * 2];
for (int i = 0, j = 0; i < input.length; i , j =2) {
output[j] = (byte)(input[i] amp; 0xff);
output[j 1] = (byte)((input[i] >> 8) amp; 0xff);
}
for (int i = 0; i < output.length; i )
System.out.format("xn",output[i]);
}
}
Чтобы понять, что вы можете прочитать эту статью WP: http://en.wikipedia.org/wiki/Endianness
Будет выведен приведенный выше исходный код 34 12 78 56 bc 9a
. Первые 2 байта ( 34 12
) представляют первое целое число и т.д. Приведенный выше исходный код кодирует целые числа в формате little endian.
Ответ №5:
/** length should be less than 4 (for int) **/
public long byteToInt(byte[] bytes, int length) {
int val = 0;
if(length>4) throw new RuntimeException("Too big to fit in int");
for (int i = 0; i < length; i ) {
val=val<<8;
val=val|(bytes[i] amp; 0xFF);
}
return val;
}
Ответ №6:
Как часто, в guava есть то, что вам нужно.
Чтобы перейти от массива байтов к int: Ints.fromBytesArray
, doc здесь
Чтобы перейти от int к массиву байтов: Ints.toByteArray
, документ здесь
Ответ №7:
У кого-то есть требование, при котором они должны читать из битов, допустим, вам нужно читать только из 3 битов, но вам нужно целое число со знаком, затем используйте следующее:
data is of type: java.util.BitSet
new BigInteger(data.toByteArray).intValue() << 32 - 3 >> 32 - 3
Магическое число 3
можно заменить количеством бит (не байтов), которое вы используете.
Ответ №8:
я думаю, что это лучший способ для преобразования в int
public int ByteToint(Byte B){
String comb;
int out=0;
comb=B "";
salida= Integer.parseInt(comb);
out=out 128;
return out;
}
первый comvert байт в строку
comb=B "";
следующий шаг — преобразование в int
out= Integer.parseInt(comb);
но байт находится в диапазоне от -128 до 127 по этой причине, я думаю, лучше использовать значение от 0 до 255, и вам нужно только сделать это:
out=out 256;
Комментарии:
1. Это неправильно. Рассмотрим байт 0x01. Ваш метод выведет 129, что неверно. 0x01 должен вывести целое число 1. Вы должны добавлять 128 только в том случае, если целое число, полученное из parseInt, отрицательно.
2. Я имел в виду, что вы должны добавить 256, а не 128. Не удалось отредактировать его впоследствии.
3. изменен post, чтобы добавить 256, поскольку это может быть полезно другим!
4. Это приводит к большому количеству приведений и созданию новых объектов (подумайте, делая это в циклах for), которые могут снизить производительность, пожалуйста, проверьте метод Integer.toString() для получения подсказок о том, как анализировать числа.
5. кроме того, при размещении кода в stackoverflow смысл заключается в том, чтобы размещать код, который легко имеет смысл. Код, который легко имеет смысл, должен иметь понятные идентификаторы. И в stackoverflow понятное обязательно означает на английском языке.