有趣的byte与0xff

背景:   最近看到这样一段代码,字节数据转字符串,起初没有看懂,为啥Integer.toHexString(0xFF & bArray[i])    这个方法里要写成0xFF & bArray[i]

这句代码的最终目的是把byte[]转换为16进制字符串,toHexString()是把一个int转换为十六进制String ,&0xFF是为了保证byte类型转int后其二进制的一致,即补零扩展...

 public String bytesToHexString(byte[] bArray) {
        StringBuffer sb = new StringBuffer(bArray.length);
        String sTemp;
        for (int i = 0; i < bArray.length; i++) {
            sTemp = Integer.toHexString(0xFF & bArray[i]);
            if (sTemp.length() < 2) {
                sb.append(0);
            }
            sb.append(sTemp.toUpperCase());
        }
        return sb.toString();
  }

看完这段解释,好像懂了又好像没懂,为啥要补零扩展?还有什么扩展呢?

一、了解基本概念原码、反码、补码

  •   原码,就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值,正数的符号位为 0、负数的符号位为 1
  •  反码,负数的反码是在其原码的基础上, 符号位不变,其余各个位取反,正数的反码是其本身
  •  补码,负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1),正数的补码就是其本身

反码是原码和补码相互转换时的临时过渡,用处不大

二、为什么计算机中要以补码形式存储数据?

      在计算机系统中数值一律用补码的形式来表示和存储。原因是:使用补码时,可以将符号位和数值位统一处理;同时加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算结果是相同的,不需要额外的硬件电路。其次存放补码可以保证计算的准确性。因为CPU只有加法,所以要将1-1转换成1+(-1)来计算。因为1是正数,所以1的反码补码都是其本身。以int类型数据为例子:

1的原/反/补码   0000 0000 0000 0000 0000 0000 0000 0001

-1的       原码   1000 0000 0000 0000 0000 0000 0000 0001

-1的       反码    1111 1111 1111   1111  1111  1111 1111  1110

-1的       补码    1111 1111 1111   1111  1111  1111 1111   1111 

将1的补码和-1的补码相加计算, 

  0000 0000 0000 0000 0000 0000 0000 0001

 1111 1111   1111   1111  1111 1111  1111  1111 

结果是

1 0000 0000 0000 0000 0000 0000 0000 0000

转换为int 类型,这时候你会发现最高位1其实丢了,因为int 类型占4个字节即32bit,所以0000 0000 0000 0000 0000 0000 0000 0000 再次转为原码还是0000 0000 0000 0000 0000 0000 0000 0000,即得到运算结果是十进制的0

三、补零扩展和补符号扩展

byte占8位,int占32位等。正因如此,当把一个低精度的数据类型转成一个高精度的数据类型时,必然会涉及到如何扩展位数的问题。这里有两种解决方案: 

  • 补零扩展:填充一定位数的0。 
  • 补符号位扩展:填充一定位数的符号位(非负数填充0,负数填充1)

举例说明,

-127的原码是 1111 1111 (注意1111 1111 表示无符号是就是255 ,有符号时就是-127 ,有符号时+127 是 0111 111)

-127的反码是 1000 0000

-127的补码是 1000 0001 计算机中存储的就是这个补码,

使用补零扩展得到结果是        0000 0000 0000 0000  0000 0000 1000 0001 正数的补码就是其本身,所以转换为10进制是129 

使用补符号扩展得到的结果是 1111  1111  1111 1111  1111  1111  1000 0001 

求这个补码的补码即原码 1000 0000 0000 0000 00000 0000 0111 1111所以转换为10进制是-127 

由此可见 对与-127,在计算机存储的是补码1000 0001,补零扩展后,为0000 0000 0000 0000  0000 0000 1000 0001,使用补零扩展能够保证二进制存储的一致性,但不能保证十进制值不变,补符号扩展,虽然得到的十进制值没有改变,但是二进制的值却变了,变成了1111  1111  1111 1111  1111  1111  1000 0001
 

四 、我们到底想要什么?

情况一,我们需要 十进制的一致性

public class Test{
    public static void main(String[] args) {
        Byte a=-127;
        int b=a;
        System.out.println(b);
    }
}
// 输出结果是-127

这是因为Java中只有有符号数,当byte扩展到short, int时,Java中的扩展方式是补符号位扩展

但是我做byte->int的转化 所有时候都只是为了保持 十进制的一致性吗?

不一定吧?好比我们拿到的文件流转成byte数组,难道我们关心的是byte数组的十进制的值是多少吗?我们关心的是其背后二进制存储的补码吧。所以大家应该能猜到为什么byte类型的数字要&0xff再赋值给int类型,其本质原因就是想保持二进制补码的一致性。

情况二,我们需要保持二进制补码的一致性

当byte要转化为int的时候,高的24位必然会补1,这样,其二进制补码其实已经不一致了,&0xff可以将高的24位置为0,低8位保持原样。这样做的目的就是为了保证二进制数据的一致性。

因为 0xFF = 1111 1111  低8位为1,高位都为0

对应的二进制为 0000 0000 0000 0000 0000 0000 1111 1111,位运算符&的特点两边都是1才是1,否则是0,故 &0xFF 可将数字的高位都置为0,低8位不变

当然拉,保证了二进制数据性的同时,如果二进制被当作byte和int来解读,其10进制的值必然是不同的,因为符号位位置已经发生了变化

猜你喜欢

转载自blog.csdn.net/neusoft2016/article/details/128206162
今日推荐