代码之家  ›  专栏  ›  技术社区  ›  sergtk

理解Java字节

  •  8
  • sergtk  · 技术社区  · 15 年前

    所以在昨天的工作中,我不得不写一个应用程序来计算AFP文件中的页数。所以我重新整理了我的mo:dca spec pdf,找到了结构化字段 BPG (Begin Page) 以及它的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 以获取无符号int值。这对于我获得一个可以用于计算跳过字节数的长度是必要的。

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

    如果我理解位 & 正确地说,您的结果等于一个仅设置了两个数字的公共位的数字。所以假设 byte b = -128 ,我们将有:

    b & 0xff // 128
    
    1000 0000-128
    1111 1111 255
    ---------
    1000 0000 128
    

    那么,我怎样才能以1000万美元兑换-128美元呢?我如何得到不太明显的二进制表示,如-72或-64?

    6 回复  |  直到 11 年前
        1
  •  18
  •   Grodriguez    15 年前

    为了获得负数的二进制表示形式,需要计算2的补码:

    • 获取正数的二进制表示形式
    • 反转所有位
    • 添加一个

    举个例子-72:

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

    因此-72的二进制(8位)表示是 10111000 .

    实际发生在您身上的是:您的文件有一个带值的字节 一千零一十一万一千 . 当解释为无符号字节(这可能是您想要的)时,这是88。

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

    通过安定 0xff 您只保留最低的8位,所以现在您的整数是 00000000 00000000 00000000 10111000 ,这是88。

        2
  •  2
  •   sepp2k    15 年前

    我想知道的是位运算在这里是如何工作的——更具体地说,我是如何得到负数的二进制表示的。

    负数的二进制表示是对应的正数位的二进制表示,加上1。此表示称为 two's complement .

        3
  •  1
  •   DarkDust    15 年前

    我猜这里的神奇之处在于,字节存储在一个更大的容器中,很可能是一个32位int。如果字节被解释为一个带符号的字节,它将被扩展为表示32位int中的相同数字,也就是说,如果字节中最重要的位(第一个位)是1,那么在32位int中,该1中剩下的所有位也都是1。ed到1(这是由于负数的表示方式,即2的补码)。

    现在,如果你 & 0xFF 这个int去掉这些1,最后得到一个表示您所读取的字节值的“正”int。

        4
  •  1
  •   Durandal    15 年前

    不确定您真正想要什么:)我假设您正在询问如何提取带符号的多字节值?首先,看看当您对单个字节进行符号扩展时会发生什么:

    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。 您已经知道如何通过使用0xff禁止符号扩展-对于多字节值,您只希望最重要字节的符号是extendet,并且希望将不太重要的字节视为无符号(例如假定网络字节顺序,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位int提取为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
    

    注意:在基于Intel的系统上,字节通常以相反的顺序存储(最低有效字节优先),因为x86体系结构将较大的实体按此顺序存储在内存中。许多源于x86的软件也在文件格式中使用它。

        5
  •  0
  •   Peter Lawrey    15 年前

    要获取无符号字节值,您也可以。

    int u = b & 0xFF;
    

    int u = b < 0 ? b + 256 : b;
    
        6
  •  0
  •   starblue    15 年前

    对于设置了位7的字节:

    unsigned_value = signed_value + 256
    

    用字节计算时,用数学方法计算模256。有符号和无符号的区别在于,为等价类选择不同的表示,而作为位模式的底层表示对于每个等价类保持不变。这也解释了为什么加法、减法和乘法的结果与位模式相同,而不管是用有符号整数还是无符号整数计算。