Перейти к основному содержимому

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

· 9 мин. чтения

1. Обзор

В этом руководстве мы рассмотрим различные подходы к преобразованию массива байтов в числовое значение ( int , long , float , double ) и наоборот.

Байт является основной единицей информации в компьютере для хранения и обработки. Примитивные типы, определенные в языке Java, представляют собой удобный способ манипулирования несколькими байтами одновременно. Следовательно, между массивом байтов и примитивными типами существует неотъемлемая связь преобразования .

Поскольку типы short и char состоят всего из двух байтов, они не требуют особого внимания. Итак, мы сосредоточимся на преобразовании между байтовым массивом и типами int , long , float и double .

2. Использование операторов сдвига

Самый простой способ преобразовать массив байтов в числовое значение — использовать операторы сдвига .

2.1. Byte Array to int и long

При преобразовании массива байтов в значение int мы используем оператор << (сдвиг влево):

int value = 0;
for (byte b : bytes) {
value = (value << 8) + (b & 0xFF);
}

Обычно длина массива байтов в приведенном выше фрагменте кода должна быть равна или меньше четырех. Это потому, что значение int занимает четыре байта. В противном случае это приведет к переполнению диапазона int .

Для проверки правильности преобразования определим две константы:

byte[] INT_BYTE_ARRAY = new byte[] {
(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE
};
int INT_VALUE = 0xCAFEBABE;

Если мы внимательно посмотрим на эти две константы, INT_BYTE_ARRAY и INT_VALUE , мы обнаружим, что они представляют собой разные представления шестнадцатеричного числа 0xCAFEBABE .

Затем давайте проверим правильность этого преобразования:

int value = convertByteArrayToIntUsingShiftOperator(INT_BYTE_ARRAY);

assertEquals(INT_VALUE, value);

Точно так же при преобразовании массива байтов в длинное значение мы можем повторно использовать приведенный выше фрагмент кода с двумя изменениями: тип значенияlong , а длина байтов должна быть равна или меньше восьми.

2.2. int и long в байтовый массив

При преобразовании значения int в массив байтов мы можем использовать оператор >> (знаковый сдвиг вправо) или >>> (беззнаковый сдвиг вправо):

byte[] bytes = new byte[Integer.BYTES];
int length = bytes.length;
for (int i = 0; i < length; i++) {
bytes[length - i - 1] = (byte) (value & 0xFF);
value >>= 8;
}

В приведенном выше фрагменте кода мы можем заменить оператор >> на оператор >>> . Это потому, что мы используем только те байты, которые изначально содержит параметр value . Таким образом, сдвиг вправо с расширением знака или расширением нуля не повлияет на конечный результат.

Затем мы можем проверить правильность приведенного выше преобразования:

byte[] bytes = convertIntToByteArrayUsingShiftOperator(INT_VALUE);

assertArrayEquals(INT_BYTE_ARRAY, bytes);

При преобразовании длинного значения в массив байтов нам нужно только изменить Integer.BYTES на Long.BYTES и убедиться, что тип значенияlong .

2.3. Массив байтов для плавания и удвоения

При преобразовании массива байтов в число с плавающей запятой мы используем метод Float.intBitsToFloat () :

// convert bytes to int
int intValue = 0;
for (byte b : bytes) {
intValue = (intValue << 8) + (b & 0xFF);
}

// convert int to float
float value = Float.intBitsToFloat(intValue);

Из приведенного выше фрагмента кода мы можем узнать, что массив байтов нельзя преобразовать напрямую в значение с плавающей запятой . По сути, это занимает два отдельных шага: во-первых, мы переходим от массива байтов к значению int , а затем интерпретируем тот же битовый шаблон в значение с плавающей запятой .

Для проверки правильности преобразования определим две константы:

byte[] FLOAT_BYTE_ARRAY = new byte[] {
(byte) 0x40, (byte) 0x48, (byte) 0xF5, (byte) 0xC3
};
float FLOAT_VALUE = 3.14F;

Затем давайте проверим правильность этого преобразования:

float value = convertByteArrayToFloatUsingShiftOperator(FLOAT_BYTE_ARRAY);

assertEquals(Float.floatToIntBits(FLOAT_VALUE), Float.floatToIntBits(value));

Точно так же мы можем использовать промежуточное длинное значение и метод Double.longBitsToDouble() для преобразования массива байтов в двойное значение .

2.4. float и double для массива байтов

При преобразовании числа с плавающей запятой в массив байтов мы можем воспользоваться методом Float.floatToIntBits() :

// convert float to int
int intValue = Float.floatToIntBits(value);

// convert int to bytes
byte[] bytes = new byte[Float.BYTES];
int length = bytes.length;
for (int i = 0; i < length; i++) {
bytes[length - i - 1] = (byte) (intValue & 0xFF);
intValue >>= 8;
}

Затем давайте проверим правильность этого преобразования:

byte[] bytes = convertFloatToByteArrayUsingShiftOperator(FLOAT_VALUE);

assertArrayEquals(FLOAT_BYTE_ARRAY, bytes);

По аналогии мы можем использовать метод Double.doubleToLongBits() для преобразования значения типа double в массив байтов . **

**

3. Использование байтового буфера

Класс java.nio.ByteBuffer предоставляет аккуратный унифицированный способ перевода между массивом байтов и числовым значением ( int , long , float , double ).

3.1. Массив байтов в числовое значение

Теперь мы используем класс ByteBuffer для преобразования массива байтов в значение типа int :

ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
buffer.put(bytes);
buffer.rewind();
int value = buffer.getInt();

Затем мы используем класс ByteBuffer для преобразования значения int в массив байтов :

ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
buffer.putInt(value);
buffer.rewind();
byte[] bytes = buffer.array();

Следует отметить, что приведенные выше два фрагмента кода следуют одному и тому же шаблону:

  • Во-первых, мы используем метод ByteBuffer.allocate(int) для получения объекта ByteBuffer с указанной емкостью.
  • Затем мы помещаем исходное значение ( массив байтов или значение int ) в объект ByteBuffer , например методы buffer.put(bytes) и buffer.putInt(value) .
  • После этого мы сбрасываем позицию объекта ByteBuffer на ноль, чтобы мы могли читать с самого начала.
  • Наконец, мы получаем целевое значение из объекта ByteBuffer , используя такие методы, как buffer.getInt() и buffer.array() .

Этот шаблон очень универсален и поддерживает преобразование типов long , float и double . Единственная модификация, которую нам нужно сделать, — это информация, относящаяся к типу.

3.2. Использование существующего массива байтов

Кроме того, метод ByteBuffer.wrap(byte[]) позволяет нам повторно использовать существующий массив байтов без создания нового:

ByteBuffer.wrap(bytes).getFloat();

Однако мы также должны отметить, что длина указанной выше переменной bytes равна или превышает размер целевого типа ( Float.BYTES ). В противном случае будет выброшено BufferUnderflowException .

4. Использование BigInteger

Основная цель класса java.math.BigInteger — представлять большие числовые значения, которые в противном случае не поместились бы в примитивный тип данных. Несмотря на то, что мы можем использовать его для преобразования между массивом байтов и примитивным значением, использование BigInteger немного тяжело для таких целей.

4.1. Byte Array to int и long

Теперь воспользуемся классом BigInteger для преобразования массива байтов в значение типа int :

int value = new BigInteger(bytes).intValue();

Точно так же класс BigInteger имеет метод longValue() для преобразования массива байтов в длинное значение:

long value = new BigInteger(bytes).longValue();

Кроме того, класс BigInteger также имеет метод intValueExact() и метод longValueExact() . Эти два метода следует использовать осторожно : если объект BigInteger выходит за пределы диапазона типа int или long , соответственно, оба метода вызовут исключение ArithmeticException .

При преобразовании значения int или long в массив байтов мы можем использовать один и тот же фрагмент кода:

byte[] bytes = BigInteger.valueOf(value).toByteArray();

Однако метод toByteArray () класса BigInteger возвращает минимальное количество байтов, не обязательно четыре или восемь байтов.

4.2. Массив байтов для плавания и удвоения

Хотя класс BigInteger имеет метод floatValue() , мы не можем использовать его для преобразования массива байтов в значение с плавающей запятой , как ожидалось. Так что нам делать? Мы можем использовать значение int в качестве промежуточного шага для преобразования массива байтов в значение с плавающей запятой :

int intValue = new BigInteger(bytes).intValue();
float value = Float.intBitsToFloat(intValue);

Точно так же мы можем преобразовать значение с плавающей запятой в массив байтов :

int intValue = Float.floatToIntBits(value);
byte[] bytes = BigInteger.valueOf(intValue).toByteArray();

Аналогичным образом, воспользовавшись преимуществами методов Double.longBitsToDouble() и Double.doubleToLongBits() , мы можем использовать класс BigInteger для преобразования между массивом байтов и двойным значением.

5. Использование гуавы

Библиотека Guava предоставляет нам удобные методы для такого преобразования.

5.1. Byte Array to int и long

В Guava класс Ints в пакете com.google.common.primitives содержит метод fromByteArray() . Следовательно, нам довольно легко преобразовать массив байтов в значение int :

int value = Ints.fromByteArray(bytes);

Класс Ints также имеет метод toByteArray() , который можно использовать для преобразования значения int в массив байтов :

byte[] bytes = Ints.toByteArray(value);

И класс Longs похож в использовании на класс Ints :

long value = Longs.fromByteArray(bytes);
byte[] bytes = Longs.toByteArray(value);

Кроме того, если мы проверим исходный код методов fromByteArray() и toByteArray() , то обнаружим, что оба метода используют операторы сдвига для выполнения своих задач .

5.2. Массив байтов для плавания и удвоения

В том же пакете также существуют классы Floats и Doubles . Но ни один из этих двух классов не поддерживает методы fromByteArray() и toByteArray() .

Однако мы можем использовать методы Float.intBitsToFloat() , Float.floatToIntBits() , Double.longBitsToDouble() и Double.doubleToLongBits() для завершения преобразования между массивом байтов и значением с плавающей запятой или двойным значением. Для краткости мы опустили здесь код.

6. Использование CommonsLang

Когда мы используем Apache Commons Lang 3 , выполнять такие преобразования немного сложно. Это связано с тем, что библиотека Commons Lang по умолчанию использует массивы байтов с прямым порядком байтов . Однако все массивы байтов , о которых мы упоминали выше, имеют порядок байтов с обратным порядком байтов. Таким образом, нам нужно преобразовать массив байтов с прямым порядком байтов в массив байтов с прямым порядком байтов и наоборот.

6.1. Byte Array to int и long

Класс Conversion в пакете org.apache.commons.lang3 предоставляет методы byteArrayToInt() и intToByteArray() .

Теперь давайте преобразуем массив байтов в значение int :

byte[] copyBytes = Arrays.copyOf(bytes, bytes.length);
ArrayUtils.reverse(copyBytes);
int value = Conversion.byteArrayToInt(copyBytes, 0, 0, 0, copyBytes.length);

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

Затем давайте преобразуем значение int в массив байтов :

byte[] bytes = new byte[Integer.BYTES];
Conversion.intToByteArray(value, 0, bytes, 0, bytes.length);
ArrayUtils.reverse(bytes);

Класс Conversion также определяет методы byteArrayToLong() и longToByteArray() . И мы можем использовать эти два метода для преобразования массива байтов в длинное значение.

6.2. Массив байтов для плавания и удвоения

Однако класс Conversion не предоставляет напрямую соответствующие методы для преобразования значения с плавающей запятой или двойного числа .

Опять же, нам нужно промежуточное целочисленное или длинное значение для преобразования между массивом байтов и значением с плавающей запятой или двойным значением.

7. Заключение

В этой статье мы проиллюстрировали различные способы преобразования массива байтов в числовое значение, используя простую Java с помощью операторов сдвига, ByteBuffer и BigInteger . Затем мы увидели соответствующие преобразования с использованием Guava и Apache Commons Lang.

Как обычно, исходный код этого руководства можно найти на GitHub .