理解Java字节码

8
昨天在工作中,我需要编写一个应用程序来计算AFP文件中的页面数。于是,我翻阅了我的MO:DCA规范PDF,并找到了结构化字段BPG(开始页)及其3字节标识符。由于该应用程序需要在AIX平台上运行,所以我决定使用Java语言编写。
为了达到最高效率,我决定只读取每个结构化字段的前6个字节,然后跳过字段中的其余字节。这样可以得到:
0: Start of field byte
1-2: 2-byte length of field
3-5: 3-byte sequence identifying the type of field

如果字段类型是BPG,我会检查并增加页面计数器,否则不会增加。然后我会跳过该字段中的剩余字节,而不是读取它们。在这里,在跳过(实际上是字段长度)时,我发现Java使用有符号字节。

我进行了一些搜索,找到了相当多有用的信息。当然,最有用的是指令要对0xff进行按位&操作,以获得无符号整数值。这对于我获取可用于计算要跳过的字节数的长度是必要的。

我现在知道,在128处,我们从-128开始倒数。我想知道的是按位运算如何工作,更具体地说,如何得出负数的二进制表示。

如果我正确理解按位&,那么你的结果等于一个数字,其中只有两个数字的公共位被设置。因此,假设byte b = -128,我们会有:

b & 0xff // 128

1000 0000-128
1111 1111 255
---------
1000 0000 128

那么,如何得到-128的二进制表示为10000000?那么如何获取一些不太明显的数字的二进制表示,例如-72或者-64?
6个回答

18
为了获得负数的二进制表示,需要计算二进制补码:
  • 获取正数的二进制表示
  • 反转所有比特位
  • 加1

我们以-72为例:

0100 1000    72
1011 0111    All bits inverted
1011 1000    Add one

所以-72的二进制(8位)表示为10111000

实际上发生的是:您的文件具有值为10111000的字节。 当将其解释为无符号字节(这可能是您想要的),它的值为88。

在Java中,当此字节用作int时(例如因为read()返回一个int或因为隐式提升),它将被解释为有符号字节,并扩展为11111111 11111111 11111111 10111000。 这是一个值为-72的整数。

通过与0xff进行AND运算,您仅保留最低的8位,因此您的整数现在为00000000 00000000 00000000 10111000,它的值为88。


+1 是因为提到了该操作在带符号扩展的 int 中进行。 - Thorbjørn Ravn Andersen
4
这正是我想要的,非常感谢。这就是我喜欢Stackoverflow的原因。 - Brian Warshaw

2
我想知道的是这里的位运算是如何工作的,更具体地说,我如何得到负数的二进制表示法。
负数的二进制表示法是对应正数按位取反并加1得到的。这种表示法称为二进制补码

1

不太确定你真正想要什么 :) 我猜你是在问如何提取一个有符号的多字节值?首先,看看当你对单个字节进行符号扩展时会发生什么:

byte[] b = new byte[] { -128 };
int i = b[0];
System.out.println(i); // prints -128!

所以,符号正确地扩展到32位,而不需要做任何特殊处理。字节1000 0000正确扩展为1111 1111 1111 1111 1111 1111 1000 0000。 您已经知道如何通过AND与0xFF抑制符号扩展-对于多字节值,您只希望最高有效字节的符号被扩展,而较不重要的字节则希望将其视为无符号(示例假定网络字节顺序,16位int值):

byte[] b = new byte[] { -128, 1 }; // 0x80, 0x01
int i = (b[0] << 8) | (b[1] & 0xFF);
System.out.println(i); // prints -32767!
System.out.println(Integer.toHexString(i)); // prints ffff8001

您需要抑制除最高位字节以外的每个字节的符号扩展,以便将有符号的32位整数提取为64位长整型:

byte[] b = new byte[] { -54, -2, -70, -66 }; // 0xca, 0xfe, 0xba, 0xbe
long l = ( b[0]         << 24) |
         ((b[1] & 0xFF) << 16) |
         ((b[2] & 0xFF) <<  8) |
         ((b[3] & 0xFF)      );
System.out.println(l); // prints -889275714
System.out.println(Long.toHexString(l)); // prints ffffffffcafebabe

注意:在基于英特尔的系统中,字节通常以相反的顺序存储(最不重要的字节在前),因为x86体系结构将较大的实体按此顺序在内存中存储。许多源自x86的软件也在文件格式中使用它。


1

我猜这里的魔法在于将字节存储在一个更大的容器中,可能是32位整数。如果将字节解释为有符号字节,则会扩展为在32位整数中表示相同数字,即如果字节的最高有效位(第一个)为1,则在该1左侧的所有位也都变成了1(这是由于负数的表示方式,即二进制补码)。

现在,如果你对该int执行& 0xFF操作,你就可以截断那些1,并得到一个“正”int,表示你读取的字节值。


0

对于设置了第7位的字节:

unsigned_value = signed_value + 256

在数学上,当您使用字节进行计算时,您会进行模256计算。有符号和无符号之间的区别是您为等价类选择不同的代表,而基础位模式表示对于每个等价类都保持不变。这也解释了为什么加法,减法和乘法具有相同的位模式结果,无论您是使用有符号还是无符号整数进行计算。


0

要获取无符号字节值,您可以采用以下两种方法。

int u = b & 0xFF;

或者

int u = b < 0 ? b + 256 : b;

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