Java中byte转int类型为什么要&0xFF?

今天查看代码,翻到MD5加密代码,如下:

public String getMD5(String source) throws Exception {
        StringBuilder sb = new StringBuilder();
        byte[] output = MessageDigest.getInstance("MD5").digest(source.getBytes("utf-8"));
        for (int i = 0; i < output.length; i++) {
            int c = output[i] & 0xFF;
            if (c < 16) {
                sb.append("0");
            }
            sb.append(Integer.toHexString(c));
        }
        return sb.toString();
    }int c = output[i] & 0xFF;
            if (c < 16) {
                sb.append("0");
            }
            sb.append(Integer.toHexString(c));
        }
        return sb.toString();
    }

为什么要&0xFF ?

各种查资料,了解到,计算机中存储都是利用二进制的补码存储的,存储数据机制:正数存储的二进制原码,负数存储的是二进制的补码。  补码是负数的绝对值反码加1。

计算机为什么用补码存储数据,当我们明白这个问题后,就可以去理解另一个衍生问题——数据溢出

首先我们看一段数据溢出的Java代码:

/*char是无符号数,16位存储,表示范围是0~2^16-1(即0~65535)*/
char ch = Character.MAX_VALUE; // ch为65535
ch += (char) 1; // 加1后,引起数据溢出,则ch为0

/*int是有符号数,32位存储,表示范围是-2^31~2^31-1(即-2147483648~2147483647)*/
int i = Integer.MAX_VALUE; // i为2147483647
i += 1; // 加1后,引起数据溢出,则i为-2147483648

为什么要使用原码,反码,补码 ?

【加法器】计算机只有加法器没有减法器(计算机只能识别0和1,也就是高点平和低电平,最底层的运算就是0和1的各种数学运算,也就是二进制运算,又因为组成最基本的输入输出的逻辑门实现二进制加法最简单,所以所有的数学运算都是建立在加法基础上再通添加各种逻辑门来改进的),两个数的减法运算会被计算机转换为加法运算。目的:为了简化计算机基本运算电路,使加减法都只需要用过加法电路实现,也就是让减去一个整数或加上一个负数这样的运算可以用加上一个整数来代替。于是改变负数存储的形式,存储成一种可以直接当成正数来相加的形式,这种形式就是补码。

模数】模数从物理意义上讲是某种计量器的容量。经常举的一个例子就是钟表,其模数是12,即每到12就重新从0开始,数学上叫取模或者求余(mod)。java中用%表示求余,例如 14 % 12  = 2。如果此时的正确时间为6点,而你的手表指向的是8点,要把表对准,有两种方法:一是把表逆时针拨两小时;二是把表顺时针拨10小时。

即8-2=6,(8+10)%12 =6

也就是说在此模数系统里面有8-2=8+10

这是因为2跟10对模数12互为补数。因此有以下结论:在模数系统,A-B或A+(-B)等价于A+[B补],

即8-2或8+(-2)=8+10

我们把10叫做-2在模12下的补码。我们把补码表示法(two's complement representation)所表示的四位存储单

元,按照从0000到1111递增,均匀分布在时钟的表盘上。如下图所示:

顺时针方向为加法,逆时针方向为减法

模为2^n:在1111处顺时针拨一格,就到了0000。用数学的方式,即1111+1=10000,进位舍弃则结果为0000。那么四位存储的模就是10000(2^4),这样用补码来表示负数就可以将加减法统一成加法来运算,简化了运算的复杂的程度。

减法转换为加法:3-1=3+(-1)=0011+1111=0010,到这里有人就有疑问了,0011+1111明明等于10010,怎么会是0010呢?还记得之前提过的最高位进位舍弃嘛,因此对于4位存储来说,进位舍弃就是0010=2。

数据溢出:当0111(7)加1时,按照我们人的思维来说,应该结果为8,但是对于机器来说则不是,因为0111(7)是四位存储所能表示的最大符号数,所以它是无法表示01000(8)的,这个时候我们就说数据溢出了。机器的思考方式显然和我们人脑不一样,机器按照上面环形图的方式,由于0111(7)加1时顺时针造成的数据溢出,那么我们可以把机器的操作想象成在0111(7)处顺时针拨了一格,我们再去对照下环形图发现这个时候指向了1000(-8)。这个过程想象成拨时针就OK了,对于1000(-8)减1也是同样的道理。

至此,我们完全可以总结一下:

1、计算机只有加法器没有减法器,两个数的减法运算会被计算机转换为加法运算,而补码正好能够解决减法转换为加法的问题。

2、防止机器发生零重码,同时解决了原码和反码不能表示-8的问题,这样极大的简化了计算机的硬件设计。

3、以循环的方式解决数据溢出的问题

现在我们回归到正题:

1byte = 1字节 = 8bit ,1int = 4字节 = 32bit

比如-12,[0000 1100]原  ,[1111 0011]反  ,[1111 0100]补码

byte-->int,就是8位变成32位

系统检测到byte转int进行运算时,按符号位补位,高位全部补1:  1111 1111 1111 1111 1111 1111 1111 0100

0xFF的二进制表示:1111 1111 ,高位全部补0: 0000 0000 0000 0000 0000 0000 1111 1111

-12的补码与0xFF进行与(&)操作最后就是:  0000 0000 0000 0000 0000 0000 1111 0100(十进制是244)

当从数字类型扩展到较宽的类型时,补零扩展还是补符号位扩展。由于Java只有有符号数,当byte扩展到short, int时,即正数都一样,因为符号位是0,所以无论如何都是补零扩展,但负数补零扩展和按符号位扩展结果完全不同。

byte[] a = new byte[10];
a[0]= -127;
int c = a[0]&0xff;

补符号数,原数值不变。

原码:1111 1111    补码:1000 0001  转成int类型,高位补1: 1111 1111 1111 1111 1111 1111 1000 0001 这个32位二进制补码表示的也是-127,显然这个两个补码表示的十进制数字依然是相同。

补零时,相当于把有符号数看成无符号数。比如-127 = 0x81,看成无符号数就是129

原码:1111 1111  补码:1000 0001 转成int类型,高位补0 : 0000 0000 0000 0000 0000 0000 1000 0001 这个32位二进制补码表示的是129

也就是说,byte转int类型时,自动转型是按符号位扩展的,这样能保证十进制的数值不会变,而&0xFF是补0扩展,这样能保证二进制存储的一致性,但是十进制数值已经发生变化了。而正数可以说是既按符号位扩展,又是补0扩展,所以在二进制存储和十进制数值上都能保证一致。

参考:

https://www.jianshu.com/p/63cc96758d20

http://www.cnblogs.com/think-in-java/p/5527389.html

https://blog.csdn.net/xmc281141947/article/details/74740061

猜你喜欢

转载自blog.csdn.net/zoujin6649/article/details/81001108
今日推荐