使用| =将字节打包成long会产生意外结果

时间:2017-04-04 23:44:14

标签: java

我试图将byte []数据连接成一个long变量。但由于某种原因,代码没有像我预期的那样工作。

我有这个字节数组,最大大小为8个字节,为64位,长度变量大小相同,所以我试图将这个数组连接成长变量。

public static void main(String[] args) {
    // TODO Auto-generated method stub

    byte[] data = new byte[]{
            (byte)0xD4,(byte)0x11,(byte)0x92,(byte)0x55,(byte)0xBC,(byte)0xF9
            };

    Long l = 0l;

    for (int i =0; i<6; i++){
        l |= data[i];           
        l <<=8;
        String lon = String.format("%064d", new BigInteger(Long.toBinaryString((long)l)));
        System.out.println(lon);
    }




}

结果是:

1111111111111111111111111111111111111111111111111101010000000000
1111111111111111111111111111111111111111110101000001000100000000
1111111111111111111111111111111111111111111111111001001000000000
1111111111111111111111111111111111111111100100100101010100000000
1111111111111111111111111111111111111111111111111011110000000000
1111111111111111111111111111111111111111111111111111100100000000

最终结果应该是

111111111111111110101000001000110010010010101011011110011111001

,即0xD4,0x11,0x92,0x55,0xBC,0xF9

1 个答案:

答案 0 :(得分:4)

Java中的

byte已签名,当您执行long |= byte时,byte的值被提升并且符号位被扩展,这实际上将所有这些高位设置为1如果byte是负值。

您可以这样做:

 l |= (data[i] & 255)

要强制它进入int并在它之前杀死该符号,然后将其提升为longHere is an example of this happening

详细

先决条件:如果术语“符号位”对您没有意义,那么您必须先阅读What is “2's Complement”?。我不会在这里解释。

考虑:

byte b = (byte)0xB5;
long n = 0l;

n |= b; // analogous to your l |= data[i]

请注意,n |= b完全等同于n = n | bJLS 15.26.2),所以我们会看一下。

因此必须首先评估n | b。但是,nb是不同的类型。

根据JLS 15.22.1

  

当运算符&amp;,^或|的两个操作数时是一种可转换(第5.1.8节)到基本整数类型的类型,首先对操作数执行二进制数字提升(第5.6.2节)。

两个操作数都可以转换为原始整数类型,因此我们咨询5.6.2以了解接下来会发生什么。这里的相关规则是:

  
      
  1. 应用扩展基元转换(第5.1.2节)来转换由以下规则指定的一个或两个操作数:

         
        
    • ...
    •   
    • 否则,如果任一操作数的类型为long,则另一个操作数将转换为long
    •   
    • ...
    •   
  2.   

好的,nlong,因此根据此b,必须使用5.1.2中指定的规则将long转换为byte。相关规则是:

  

将有符号整数值的扩展转换为整数类型T只需对整数值的二进制补码表示进行符号扩展,以填充更宽的格式。

long是一个带符号的整数值,它被转换为b = (byte)0xB5 10110101 b widened to long 111 ... 1111111110110101 n 000 ... 0000000000000000 n | b 111 ... 1111111110110101 ,因此符号位(最高位)只是向左扩展以填充空格。所以这就是我们的例子中发生的事情(想象64位,我只是在节省空间):

n | b

因此0xFFFFFFFFFFFFFFB5评估为0x00000000000000B5不评估 1。也就是说,当该符号位被扩展并且应用了OR操作时,你所有那些byte基本上覆盖了你已经存在的前一个字节中的所有位,并且那么,最终结果是不正确的。

这是long | byte被签名和Java需要long | long在执行计算之前转换为n = n | (long)b; 的所有结果。

如果你不清楚这里发生的隐式转换,这里是显式版本:

byte b = (byte)0xB5;
long n = 0l;

n |= (b & 255);

解决方法详情

所以现在考虑“解决方法”:

b & 255

所以在这里,我们首先评估255

因此,从JLS 3.10.1我们可以看到文字int的类型为byte & int

这给我们留下了int。虽然我们引用了与5.6.2

略有不同的情况,但规则与上述规则大致相同
  

否则,两个操作数都将转换为byte类型。

因此,根据这些规则int必须首先转换为(byte)0xB5 10110101 promote to int 11111111111111111111111110110101 (sign extended) 255 00000000000000000000000011111111 & 00000000000000000000000010110101 。所以在这种情况下我们有:

int

结果是n | the byte we just converted,已签名,但正如您所见,现在是一个正数,其符号位为0

然后下一步是评估int。因此,根据上述规则,新long被扩展为b & 255 00000000000000000000000010110101 convert to long 000 ... 0000000000000000000000000010110101 n 000 ... 0000000000000000000000000000000000 n | (b & 255) 000 ... 0000000000000000000000000010110101 ,符号位扩展,但这一次:

b

现在我们得到了预期的价值。

该解决方法的工作原理是将int转换为long作为中间步骤并将高24位设置为0,从而让我们将 转换为n = n | (long)((int)b & 255); 没有原始标志位妨碍。

如果你不清楚这里发生的隐式转换,这里是显式版本:

1

其他东西

也像maraca在评论中提到的那样,在你的循环中交换前两行,否则你最终会将整个事物向左移动到最左边8位(这就是你的低8位为零的原因)。 / p>

另外,我注意到您的预期最终结果是使用前导-1L填充的。如果这就是你想要的结果,你可以从0L而不是{{1}}开始(除了其他修正)。