这个Java 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编写的端点中?

Java中的一个byte以8位二进制补码格式表示。 如果你有一个int在128 – 255范围内并且你将它转换为一个byte ,那么它将成为一个负值的byte (介于-1和-128之间)。

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

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

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

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

那么在将一些内容转储到OutputStream时,Java级别实际发生了什么? 在检查JavaDoc以寻找编写字节数组的方法时 ,我们看到所有这些告诉我们的是字节是通过流发送的。 没什么重要的。 但是当你检查文件中的int作为参数时 ,你会看到它详细说明了这个int是如何实际写出的:低位8位作为一个字节通过流发送,而高位24位bits(在Java中具有32位表示的int)被简单地忽略。

到接收方。 你有一个InputStream。 除非您使用直接读入字节数组的方法之一,否则您将获得一个int。 与文档说的一样 ,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获取正确的字节值。 真正令人担心的是,如果客户端可以使用任何编程语言编写并在任何平台上运行,那么您需要在文档中清楚地了解接收到的字节的含义以及它们是否很long ,这是怎么回事被编码。 清楚地表明编码是用Java完成的,使用ByteBufferputLong方法在特定的字节序中。 只有这样,他们才能获得信息(结合Java规范)绝对确定如何解释这些字节。

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