这个Java ByteBuffer的行为有解释吗?

时间:2011-11-14 12:58:08

标签: java bytearray byte inputstream bytebuffer

我需要将数值转换为字节数组。例如,要将long转换为byte数组,我有这个方法:

public static byte[] longToBytes(long l) {
  ByteBuffer buff = ByteBuffer.allocate(8);

  buff.order(ByteOrder.BIG_ENDIAN);

  buff.putLong(l);

  return buff.array();
}

这很简单 - 花一点时间,分配一个可以容纳它的数组,然后把它扔进去。无论l的值是什么,我都会得到一个8字节的数组,然后我可以按照预期处理和使用它。就我而言,我正在创建一个自定义二进制格式,然后通过网络传输它。

当我使用值773450364调用此方法时,我得到一个数组[0 0 0 0 46 25 -22 124]。我的代码也将字节数组转换回它们的数值:

public static Long bytesToLong(byte[] aBytes, int start) {
  byte[] b = new byte[8];

  b[0] = aBytes[start + 0];
  b[1] = aBytes[start + 1];
  b[2] = aBytes[start + 2];
  b[3] = aBytes[start + 3];
  b[4] = aBytes[start + 4];
  b[5] = aBytes[start + 5];
  b[6] = aBytes[start + 6];
  b[7] = aBytes[start + 7];

  ByteBuffer buf = ByteBuffer.wrap(b);
 return buf.getLong();
}

当我将数组从其他方法传回到此方法时,我得到773450364,这是正确的。

现在,我通过TCP将此数组传输到另一个Java客户端。 java.io.InputStream.read()方法的文档说它返回0到255之间的int值,除非到达流的末尾并返回-1。但是,当我使用它来填充字节数组时,我继续在接收端获得负值。我怀疑这与溢出有关(255的值不适合Java字节,所以当我把它放入字节数组时,它会溢出并变为负数。)

这让我想到了我的问题。负数的存在使我感到担忧。现在,我正在开发一个应用程序的Java端,其中一个字节介于-128和127之间。另一个端点可能是C,C ++,Python,Java,C#......谁知道。我不确定某些字节数组中负值的存在会如何影响处理。 除了记录这种行为之外,我可以/应该做些什么来使自己和未来的开发人员更容易在这个系统上工作,特别是在非Java编写的端点中?

3 个答案:

答案 0 :(得分:6)

Java中的byte以8位two's complement格式表示。如果您的int在128 - 255范围内并且将其转换为byte,那么它将变为byte且值为负值(介于-1和-128之间) )。

读取一个字节后,在将其强制转换为byte 之前,必须检查它是否为-1 。该方法返回int而不是byte的原因是允许您在将其转换为byte之前检查流末尾。

另一件事:为什么要在aBytes方法中复制bytesToLong数组?您可以大大简化该方法并保存不必要的副本:

public static Long bytesToLong(byte[] aBytes, int start) {
    return ByteBuffer.wrap(aBytes, start, 8).order(ByteOrder.BIG_ENDIAN).getLong();
}

答案 1 :(得分:1)

您的发送和接收端点目前都是用Java实现的。可以想象,您在发送端使用OutputStream,在接收端使用InputStream。假设我们可以暂时信任底层套接字实现细节,我们将考虑通过套接字发送的任何字节到达其目的地完全相同。

那么在将一些内容转储到OutputStream时,Java级别实际发生了什么?检查the JavaDoc for a method writing a byte array时,我们发现所有这些都告诉我们是通过流发送字节。没什么重要的。但是当你检查method taking an int as argument的doc时,你会看到它详细说明了这个int是如何实际写出的:低位8位作为一个字节通过流发送,而高位24位(在Java中使用32位表示的int)将被忽略。

到接收方。你有一个InputStream。除非你使用one of the methods reading directly into a byte array,否则你将得到一个int。 Like the doc says,int将是0到255之间的值,或者如果已到达流的末尾则为-1。这是重要的一点。一方面,我们希望从InputStream中读取单个字节的每个可能位模式。但是我们还必须有一些方法来检测读取何时不能返回有意义的值。这就是为什么该方法返回一个int而不是一个字节的原因...... -1值是表示已到达流末尾的标志。如果你得到的不是-1,那么唯一感兴趣的是低8位。由于这些可以是任何位模式,因此它们的十进制值范围为-128到127(含)。当你直接读取一个字节数组而不是每个int的int时,就会为你完成“修剪”。因此,你会看到那些负面价值是有意义的。也就是说,由于Java将字节表示为带符号的十进制数,因此它们只是负数。唯一感兴趣的是实际的位模式。对于你所关心的一切,它可以代表0到255或1000到1255的值。

一次使用一个字节的典型InputStream读取循环将如下所示:

InputStream ips = ...;
int read = 0;
while((read = ips.read()) != -1) {
    byte b = (byte)read;
    //b will now have a bit pattern ranging from 0x00 to 0xff in hex, or -128 to 127 in two-complement signed representation
}

运行时,以下(使用Java 7 int literals)将会有所启发:

public class Main {

    public static void main(String[] args) {

        final int i1 = Ox00_00_00_fe;
        final int i1 = Ox80_00_00_fe;

        final byte b1 = (byte)i1;
        final byte b2 = (byte)i2;

        System.out.println(i1);
        System.out.println(i2);

        System.out.println(b1);
        System.out.println(b2);

        final int what = Ox12_34_56_fe;
        final byte the_f = (byte)what;

        System.out.println(what);
        System.out.println(the_f);

    }

}

从中可以清楚地看出,从int到byte的转换只会丢弃除了最低有效8位之外的任何内容。因此int可以是正数或负数,它不会对字节值产生任何影响。只有最后8位。

长话短说:您从InputStream获取了正确的字节值。真正令人担心的是,如果客户端可以使用任何编程语言编写并在任何平台上运行,那么您需要在文档中清楚地了解接收到的字节的含义以及它们是否为{{1} ,这是如何编码的。使用long的{​​{1}}方法以特定的字节顺序清楚地表明编码是在Java中完成的。只有这样,他们才能获得信息(结合Java规范)绝对确定如何解释这些字节。

答案 2 :(得分:0)

如果你的所有数据都是big-endian,你可以省去所有这些麻烦并使用DataOutputStream。它拥有你所需要的一切。