Преобразование массива байтов в целое число в Java и наоборот

#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 to int с расширением 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 понятное обязательно означает на английском языке.