HOWTO · Java
Java 中将字节数组转换为整数
本教程演示如何在 Java 中将字节数组转换为 int
本页内容
在 Java 中处理网络协议、文件 I/O 或二进制数据格式时,将字节数组转换为整数是一项常见任务。正确的方法取决于这些字节代表什么:有时是几个字节编码一个 int,有时每个字节都是独立的值。本文介绍 Java 的核心方法——ByteBuffer、DataInputStream、手动位运算和 BigInteger——以及决定结果是否正确的字节序和有符号性规则。
使用 ByteBuffer 将四个字节转换为一个 int
ByteBuffer(位于 java.nio 包中)是完成此转换最直接的 Java 核心工具。wrap 方法创建一个与数组共享存储空间的缓冲区,getInt() 从缓冲区当前位置开始读取四个字节,并将其解释为一个 int。运行下面的示例(代码中打印的标签保持英文):
本节中的代码以及显示的标签或输出仍为英文。
import java.nio.ByteBuffer;
public class ByteArrayToInt {
public static void main(String[] args) {
byte[] byteArray = {0x00, 0x01, 0x03, 0x10};
int intValue = ByteBuffer.wrap(byteArray).getInt();
System.out.println("Converted Integer Value: " + intValue);
}
}
输出:
Converted Integer Value: 66320
ByteBuffer 在所有平台上默认都是大端序:数组中的第一个字节被视为最高有效字节。如果数据是以小端序写入的,请在读取之前使用 ByteOrder 显式选择该顺序:
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class ByteArrayToIntOrder {
public static void main(String[] args) {
byte[] byteArray = {0x00, 0x01, 0x03, 0x10};
int bigEndianValue = ByteBuffer.wrap(byteArray).getInt();
int littleEndianValue =
ByteBuffer.wrap(byteArray).order(ByteOrder.LITTLE_ENDIAN).getInt();
System.out.println("big-endian: " + bigEndianValue);
System.out.println("little-endian: " + littleEndianValue);
}
}
输出:
big-endian: 66320
little-endian: 268632320
这里有两个细节很重要。第一,getInt() 在缓冲区的当前位置读取并将其向前推进四个字节,因此在同一个缓冲区上调用两次会读取接下来的四个字节;而新的 wrap 总是从位置 0 开始。第二,当剩余字节少于四个时,getInt() 会抛出 BufferUnderflowException,因此读取之前要先检查输入长度。
使用 DataInputStream 读取四个字节
当字节通过流到达时——或者当你需要按顺序读取多个基本类型值时——DataInputStream 是合适的选择。将数组包装在 ByteArrayInputStream 中,再将其包装在 DataInputStream 中,然后调用 readInt()(示例输出中的标签保持英文):
本节中的代码以及显示的标签或输出仍为英文。
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
public class ByteArrayToIntWithDataInputStream {
public static void main(String[] args) throws IOException {
byte[] byteArray = {0x00, 0x01, 0x03, 0x10};
try (DataInputStream input =
new DataInputStream(new ByteArrayInputStream(byteArray))) {
int intValue = input.readInt();
System.out.println("Converted Integer Value: " + intValue);
}
}
}
输出:
Converted Integer Value: 66320
无论平台如何,readInt() 始终按大端序解释接下来的四个字节。当流中剩余字节少于四个时,它会抛出 EOFException,你可以捕获该异常来报告输入过短,而不是让程序崩溃:
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
public class ByteArrayToIntShortInput {
public static void main(String[] args) throws IOException {
byte[] byteArray = {0x03, 0x10};
try (DataInputStream input =
new DataInputStream(new ByteArrayInputStream(byteArray))) {
int intValue = input.readInt();
System.out.println("Converted Integer Value: " + intValue);
} catch (EOFException e) {
System.out.println("readInt() needs four bytes, but only " + byteArray.length + " remain");
}
}
}
输出:
readInt() needs four bytes, but only 2 remain
通过字节移位构建 int
自己对字节进行移位可以清楚地展示转换的工作原理,并让你完全控制顺序和长度。对每个字节,将累积值左移 8 位,再与下一个字节进行或运算(示例输出中的标签保持英文):
本节中的代码以及显示的标签或输出仍为英文。
public class ByteArrayToIntManually {
public static void main(String[] args) {
byte[] byteArray = {0x00, 0x01, 0x03, 0x10};
int intValue = 0;
for (byte b : byteArray) {
intValue = (intValue << 8) | (b & 0xFF);
}
System.out.println("Converted Integer Value: " + intValue);
}
}
输出:
Converted Integer Value: 66320
掩码 b & 0xFF 是必需的,因为 Java 的 byte 类型是有符号的:0x80 被存储为 -128。如果没有掩码,| 会合并 0xFFFFFF80 而不是 0x00000080,从而破坏所有更高位的字节。这个循环按大端序组装字节——第一个数组元素是最高有效字节——并假设数组恰好包含四个字节。如果需要不同的顺序,就反向遍历数组;如果字节更多,就累积到 long 而不是 int 中。
使用 BigInteger 解释字节数组
BigInteger 的构造函数直接接受字节数组,并将其解释为任意长度的有符号、大端序、二进制补码数字。然后 intValue() 将结果收窄为 int(示例输出中的标签保持英文):
本节中的代码以及显示的标签或输出仍为英文。
import java.math.BigInteger;
public class ByteArrayToIntWithBigInteger {
public static void main(String[] args) {
byte[] byteArray = {0x00, 0x01, 0x03, 0x10};
int intValue = new BigInteger(byteArray).intValue();
System.out.println("Converted Integer Value: " + intValue);
}
}
输出:
Converted Integer Value: 66320
由于解释是有符号的,前导 1 位会产生小于 0 的值;将符号值 1 作为构造函数的第一个参数传入,即可得到无符号解释。请注意,intValue() 只保留低 32 位——它会静默截断无法放入 int 的值:
import java.math.BigInteger;
public class BigIntegerSignAndRange {
public static void main(String[] args) {
byte[] allOnes = {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
System.out.println("signed: " + new BigInteger(allOnes));
System.out.println("unsigned: " + new BigInteger(1, allOnes));
System.out.println("narrowed to int: " + new BigInteger(1, allOnes).intValue());
}
}
输出:
signed: -1
unsigned: 4294967295
narrowed to int: -1
当数组长度超过四个字节,或者在收窄之前需要无符号数值时,BigInteger 是自然的选择。
分别转换每个字节
上面的所有方法都是把多个字节组合成一个数字。如果每个字节都是独立的值——传感器读数、颜色通道、协议字段——则应逐个转换元素。注意两者的区别:对两个字节使用 getShort() 或 readShort() 得到的是一个组合的 short,而不是分别转换每个字节。
还要记住,Java 的 byte 类型是有符号的,范围为 -128 到 127。当意图是 0 到 255 的无符号值时,使用 Byte.toUnsignedInt(自 Java 8 起可用)或等效的 b & 0xFF 掩码(示例输出中的标签保持英文):
本节中的代码以及显示的标签或输出仍为英文。
public class PrintEachByte {
public static void main(String[] args) {
byte[] byteArray = {0x03, 0x04, (byte) 0x80, (byte) 0xFF};
for (byte b : byteArray) {
System.out.println("signed: " + b + " unsigned: " + Byte.toUnsignedInt(b));
}
}
}
输出:
signed: 3 unsigned: 3
signed: 4 unsigned: 4
signed: -128 unsigned: 128
signed: -1 unsigned: 255
选择方法并处理边界情况
每种方法适用于不同的场景:
ByteBuffer是从数组读取四字节int的首选:它紧凑,通过ByteOrder支持两种字节序,并提供getLong()、getShort()等方法处理其他宽度。DataInputStream适合已经在使用流、并需要按顺序读取多个类型化值的代码;它的读取始终是大端序。- 手动移位要多写几行代码,但能精确控制顺序、偏移量和宽度,且不需要缓冲区或流对象。
BigInteger可以处理长于四个字节的数组,并在收窄之前进行显式的有符号或无符号解释。- 当每个元素本身就是独立的值、而不是一个数字的组成部分时,使用
Byte.toUnsignedInt逐字节转换。
无论选择哪种方法,转换之前请检查以下几点:
- **输入长度:**对短于四个字节的数组进行四字节读取会失败——
ByteBuffer抛出BufferUnderflowException,DataInputStream抛出EOFException,而手动代码则会抛出ArrayIndexOutOfBoundsException或产生静默错误的结果。 - **字节序:**读取端的顺序必须与字节写入时的顺序一致;
ByteBuffer默认为大端序,可以用ByteOrder切换,而DataInputStream的读取始终是大端序,没有顺序选项。 - 有符号性和范围:
getInt()和readInt()返回有符号int,因此超过Integer.MAX_VALUE的无符号 32 位值会显示为小于 0 的数。当源数据是无符号时,使用Integer.toUnsignedLong(intValue)或累积到long中。 - **缓冲区位置:**重用
ByteBuffer会从其当前位置继续读取,而不是从头开始;调用rewind()或再次wrap以重新读取数组。