1. 概述
在本教程中,我们将探讨将字节数组转换为数值(int、long、float、double)的不同方法,反之亦然。
字节是计算机存储和处理信息的基本单位。Java语言中定义的基本类型是同时操作多个字节的便捷方式。因此,字节数组和原始类型之间存在着一种内在的转换关系。
由于short和char类型只包含两个字节,因此不需要太多关注。因此,我们将重点关注字节数组与int、long、float和double类型之间的转换。
2. 使用移位运算符
将字节数组转换为数值的最直接方法是使用移位运算符。
2.1 字节数组到int和long
将字节数组转换为int值时,我们使用«(左移)运算符:
int value = 0;
for (byte b : bytes) {
value = (value << 8) + (b & 0xFF);
}
通常,上述代码片段中bytes数组的长度应等于或小于4。这是因为一个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值时,我们可以重用上面的代码片段并进行两处修改:值的类型为long型且字节长度应等于或小于8。
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);
将long值转换为字节数组时,我们只需要将Integer.BYTES更改为Long.BYTES并确保值的类型为long。
2.3 字节数组到float和double
将字节数组转换为浮点数时,我们使用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值,然后将相同的位模式解释为float值。
为了验证转换的正确性,让我们定义两个常量:
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));
同样,我们可以利用中间long值和Double.longBitsToDouble()方法将字节数组转换为double值。
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. 使用ByteBuffer
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对象的position重置为0,以便我们可以从头开始读取。
- 最后,我们使用buffer.getInt()和buffer.array()等方法从ByteBuffer对象中获取目标值。
这种模式非常通用,它支持long、float和double类型的转换,我们唯一需要做的修改是与类型相关的信息。
3.2 使用现有的字节数组
此外,ByteBuffer.wrap(byte[])方法允许我们重用现有字节数组而无需创建新数组:
ByteBuffer.wrap(bytes).getFloat();
但是,我们还应该注意,上面的bytes变量的长度等于或大于目标类型(Float.BYTES)的大小。否则,它将抛出BufferUnderflowException。
4. 使用BigInteger
java.math.BigInteger类的主要目的是表示大数值,否则这些数值将不适合原始数据类型。尽管我们可以使用它在字节数组和原始值之间进行转换,但使用BigInteger对于这种目的来说有点繁重。
4.1 字节数组到int和long
现在,让我们使用BigInteger类将字节数组转换为int值:
int value = new BigInteger(bytes).intValue();
类似地,BigInteger类有一个longValue()方法来将字节数组转换为long值:
long value = new BigInteger(bytes).longValue();
此外,BigInteger类还有一个intValueExact()方法和一个longValueExact()方法。应谨慎使用这两个方法:如果BigInteger对象分别超出int或long类型的范围,则这两个方法都将抛出ArithmeticException。
将int或long值转换为字节数组时,我们可以使用相同的代码片段:
byte[] bytes = BigInteger.valueOf(value).toByteArray();
但是,BigInteger类的toByteArray()方法返回的是最小字节数,不一定是4个或8个字节。
4.2 字节数组到float和double
虽然BigInteger类有一个floatValue()方法,但我们不能像预期的那样使用它来将字节数组转换为浮点值。那么,我们应该怎么做呢?我们可以使用int值作为将字节数组转换为float值的中间步骤:
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类在字节数组和double值之间进行转换。
5. 使用Guava
Guava库为我们提供了方便的方法来进行这种转换。
5.1 字节数组到int和long
在Guava中,com.google.common.primitives包中的Ints类包含一个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 字节数组到float和double
在同一个包中还存在Floats和Doubles类。但是,这两个类都不支持fromByteArray()和toByteArray()方法。
但是,我们可以利用Float.intBitsToFloat()、Float.floatToIntBits()、Double.longBitsToDouble()和Double.doubleToLongBits()方法来完成字节数组与float或double之间的转换。为简洁起见,我们省略了此处的代码。
6. 使用Commons Lang
当我们使用Apache Commons Lang3时,进行这些类型的转换有点复杂。这是因为Commons Lang库默认使用小端字节数组。但是,我们上面提到的字节数组都是大端顺序的。因此,我们需要将大端字节数组转换为小端字节数组,反之亦然。
6.1 字节数组到int和long
org.apache.commons.lang3包中的Conversion类提供了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()方法。并且,我们可以使用这两种方法在字节数组和long值之间进行转换。
6.2 字节数组到float和double
但是,Conversion类并没有直接提供相应的方法来转换float或double值。
同样,我们需要一个中间int或long值来在字节数组和float或double值之间进行转换。
7. 总结
在本文中,我们说明了使用普通Java通过移位运算符、ByteBuffer和BigInteger将字节数组转换为数值的各种方法。然后,我们看到了使用Guava和Apache Commons Lang进行的相应转换。
与往常一样,本教程的完整源代码可在GitHub上获得。