【问题标题】:Understanding Java bytes理解 Java 字节
【发布时间】:2011-04-20 05:42:41
【问题描述】:

所以昨天在工作中,我不得不编写一个应用程序来计算 AFP 文件中的页数。所以我翻阅了我的 MO:DCA 规范 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 以获得 unsigned int 值。这对于我获得可用于计算要跳过的字节数的长度是必要的。

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

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

b & 0xff // 128

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

那么我如何以 -128 获得 1000 0000?我如何获得像 -72 或 -64 这样不太明显的东西的二进制表示?

【问题讨论】:

    标签: java binary byte


    【解决方案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 进行与运算,您只保留了最低 8 位,因此您的整数现在是 00000000 00000000 00000000 10111000,即 88。

    【讨论】:

    • +1 表示操作发生在带有符号扩展的 int 中。
    • 这正是我所追求的,非常感谢。这就是我喜欢 Stackoverflow 的原因。
    【解决方案2】:

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

    负数的二进制表示是对应的正数位翻转加 1。这种表示称为two's complement

    【讨论】:

      【解决方案3】:

      我想这里的神奇之处在于字节存储在一个更大的容器中,可能是一个 32 位的 int。如果字节被解释为有符号字节,它将被扩展以表示 32 位 int 中的相同数字,也就是说,如果字节的最高有效位(第一个)是 1,那么在 32 位 int 中该 1 左侧的位也变为 1(这是由于负数的表示方式,二进制补码)。

      现在,如果你 & 0xFF 那个 int 你切断那些 1 并最终得到一个“正” int 代表你读过的字节值。

      【讨论】:

        【解决方案4】:

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

        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'ing 与 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 位 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】:

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

          int u = b & 0xFF;
          

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

          【讨论】:

            【解决方案6】:

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

            unsigned_value = signed_value + 256
            

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

            【讨论】:

              猜你喜欢
              • 2010-12-05
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-01-19
              • 2013-01-22
              • 2018-10-08
              • 1970-01-01
              相关资源
              最近更新 更多