如何将一个由4个字节组成的数组转换为整数?

28

我希望进行一次转换而不需要使用一些依赖于具体实现的技巧。有什么建议吗?


2
你想要转换什么?你的问题不够具体。你的数组中有哪四个字节? - bmargulies
7个回答

80

你需要知道你的字节序是大端还是小端。

假设(像@WhiteFang34一样)bytes是长度为4的byte[],那么...

大端序:

int x = java.nio.ByteBuffer.wrap(bytes).getInt();

小端序:

int x = java.nio.ByteBuffer.wrap(bytes).order(java.nio.ByteOrder.LITTLE_ENDIAN).getInt();

1
+1 这个确实可以工作,并提供了一个良好的小端支持选项。我在想Java是否有支持这样做的功能。虽然它创建了一个对象来进行这种路线,但它并不是很高效,尽管我怀疑对于大多数用途来说这并不重要。 - WhiteFang34

28

假设 bytes 是一个大端序整数的字节数组,通常用于网络编程:

int value = ((bytes[0] & 0xFF) << 24) | ((bytes[1] & 0xFF) << 16)
        | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF);

& 0xFF是必需的,因为在Java中byte是带符号的,在这里需要保留有符号位。您可以使用以下代码反转此过程:

& 0xFF是必要的,因为Java中的byte是有符号的,而这里需要保留有符号位。您可以使用以下代码反转此过程:

bytes[0] = (byte) ((value >> 24) & 0xFF);
bytes[1] = (byte) ((value >> 16) & 0xFF);
bytes[2] = (byte) ((value >> 8) & 0xFF);
bytes[3] = (byte) (value & 0xFF);

我可能看错了,但我认为你写的是大端序。 - David Z
@David:没错,谢谢。我第一次确实标错了。 - WhiteFang34
2
byte[]转换为int时,为什么需要第一个& 0xff?不是通过<< 24删除了有符号值的前导1吗?在我看来,在这种情况下,((bytes[0] & 0xFF) << 24)((bytes[0] << 24)相同。如果byte[0]是有符号的,则会将其转换为有符号整数,但<< 24除原始字节的8位外,还会删除其他所有内容。此外,我认为使用& 0xff是为了删除带有值1的前导位,以便它们不会与先前操作的0位进行OR运算。难道说必须保留符号位才需要使用& 0xff吗? - petrn

6

您需要指定数组的字节顺序,但是假设bytes[0]是最高位字节,则:

int res = ((bytes[0] & 0xff) << 24) | ((bytes[1] & 0xff) << 16) |
          ((bytes[2] & 0xff) << 8)  | (bytes[3] & 0xff);

假设您使用反向算法创建字节数组,此代码是100%可移植的。


在某些语言中,您可以在本机整数类型和字节数组类型之间进行转换...然后发现不同的体系结构以不同的顺序存储整数的字节,这会导致字节顺序问题。

在Java中,您无法进行此转换。因此,在Java到Java通信中,这不应该是一个问题。

但是,如果您正在发送或接收来自(例如)C或C ++实现的某个远程应用程序的数据包,则需要“知道”网络数据包中使用的字节顺序。了解/弄清楚此信息的一些替代策略包括:

  • 每个人都使用“网络顺序”(big-endian)对于上述示例代码中的内容。小端机器上的非Java应用程序需要翻转字节。

  • 发送方找出接收方期望的顺序,并在组装数据时使用该顺序。

  • 接收方通过数据包中的标志弄清发送方使用的顺序,并相应地解码。

第一种方法最简单且最广泛使用,尽管如果发送方和接收方都是小端的,则会导致2次不必要的字节顺序转换。

请参见 http://en.wikipedia.org/wiki/Endianness


有没有通过标准库完成这个操作的方法?我不太确定假设发送数据包的机器的字节顺序是否正确... - alexgolec
1
@Alex:机器的字节顺序在这个计算中并不重要,重要的是你数组的字节顺序。即使你使用标准库(如果有标准方法可以做到这一点...我不记得有),你也必须指定它。 - David Z
你需要对每个字节执行 & 0xFF 操作,因为在 Java 中 byte 是有符号的。对于任何使用字节的第一个位的数字,有符号位会妨碍操作。例如,尝试从整数 384 反向转换为字节数组时,当你运行代码以获取整数时,你将得到 -128 而不是 384。 - WhiteFang34
现在需要使用括号来保留对字节的& 0xFF操作,否则<<会优先执行。如果没有它们,您将在我的示例中获得128而不是384 :) - WhiteFang34

6

不确定这是否是正确的Java语法,但以下是否可行:

int value = 0;
for (i = 0; i <= 3; i++)
    value = (value << 8) + (bytes[i] & 0xFF);

2
你应该对 bytes[i] 进行 0xff 操作,因为在 Java 中,一个 byte 可能是负数,如果不进行操作,Java 将把它转换为负整数,而不是 0-255 之间的值。 - Yanick Rochon
这段代码不起作用,它总是返回0。为了避免运算符优先级的问题,您需要加上括号:value = (value << 8) + (bytes[i] & 0xFF); - WhiteFang34

1

假设你的byte[]来自某个地方,比如一个流,你可以使用

DataInputStream dis = ... // can wrap a new ByteArrayInputStream(bytes)
int num = dis.readInt(); // assume big-endian.

或者

ByteChannel bc = ... // can be a SocketChannel
ByteBuffer bb = ByteBuffer.allocate(64*1024);

bc.read(bb);
bb.flip();
if (bb.remaining()<4) // not enough data

int num = bb.getInt();

当你发送数据时,你应该知道你是发送大端还是小端。你必须假设其他事情,比如你是否正在发送4字节有符号整数。二进制协议充满了假设。(这使它比文本更紧凑和更快,但也更脆弱)

如果你不想做太多假设,就发送文本。


1
我们也可以使用以下方法使其更具动态性,即字节数组大小
大端格式:
public static int pareAsBigEndianByteArray(byte[] bytes) {
    int factor = bytes.length - 1;
    int result = 0;
    for (int i = 0; i < bytes.length; i++) {
        if (i == 0) {
            result |= bytes[i] << (8 * factor--);
        } else {
            result |= bytes[i] << (8 * factor--);
        }
    }
    return result;
}

小端字节序格式:
public static int pareAsLittleEndianByteArray(byte[] bytes) {
    int result = 0;
    for (int i = 0; i < bytes.length; i++) {
        if (i == 0) {
            result |= bytes[i] << (8 * i);
        } else {
            result |= bytes[i] << (8 * i);
        }
    }
    return result;
}

这将有助于您将字节转换为整数值。

-2
public static int toInt( byte[] bytes ) {
int result = 0;
for (int i=0; i<3; i++) {
  result = ( result << 8 ) - Byte.MIN_VALUE + (int) bytes[i];
}
return result;
}

这个不起作用。一个全零的字节数组返回8421504。 - WhiteFang34

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接