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 .