1.超过自身位数的移位
我们知道,int类型占用4字节,32位,而long类型占用8字节,64位。那么,如果将int类型(long类型)移动超过31位(63位)便失去了意义,因为用通俗的话来说,就是“全移走了”。不过幸运的是,系统对这种情况做了处理。
是怎么处理的呢?普遍都是这样认为的:如果左侧操作数是int类型,会对右侧操作数进行除数为32的求余运算,如果左侧操作数为long类型,会对右侧操作数进行除数为64的求余运算。是的,当要移位的个数为正数时是这样的,但当要移位的个数为负数时却不正确。
例如,假如有如下的赋值运算:
int i = 5 << -10;
-10对32取余还是-10,向左移动-10位,该怎么移动?
实际上,当左侧操作数为int时类型时,右侧操作数只有低5位是有效的(低5位的范围是0~31),也就是说可以看作右侧操作数会先与掩码0x1f(00011111)做与运算,然后左侧操作数再移动相应的位数。类似地,当左侧操作数为long类型时,右侧操作数只有低6位是有效的,可以看作右侧操作数先与掩码0x3f(00111111)做与运算,然后再移动相应的位数。
-10的补码为:
1111 1111 1111 1111 1111 1111 1111 0110
取其低5位,结果为:
1 0110
这个值就是22,也就是相当于:
int i = 5 << 22;
因此,不要把移位运算右侧的操作数与求余运算联系在一起,那是不完全正确的。
2.移位运算与乘除运算
由于数据采用二进制来表示,因此就会普遍存在这样的想法:左移一位就相当于乘以2,而右移一位就相当于除以2,这种想法正确吗?
在Java中,当两个操作数都是整型的时候,结果也是整型的。如果不能整除,则结果是向0舍入的,也就是说,向靠近0的方向取值。如:
9/2的结果为4
-9/2的结果为-4
而对移位运算来说:
9>>1的结果为4
-9>>1的结果为-5(特殊,移位向下舍入)
3.永远的-1
本质是类型的自动提升
“>>>”为无符号右移运算符,其与“>>”不同的是,“>>>”是以0来填补左侧移出的空位,而“>>”是以符号位来填补左侧移出的空位。如果是正数,“>>”与“>>>”是相同的,因为都是用0来补位的,如果是负数,“>>>”就可能移出正数值来。
int a = -1;
for(int i =1;i<=Integer.SIZE;i++){
a>>>=1;
System.out.println(a);
}
short b = -1;//换成byte也是一样
for(int i =1;i<=Short.SIZE;i++){
b>>>=1;
System.out.println(b);
}
对于int类型变量而言,移位产生了int类型变量的最大值2147483647(2^31),以后每移动一位,值就减半,直到为0。对于byte类型变量,值始终是-1,为什么?
int类型变量-1的补码是:
1111 1111 1111 1111 1111 1111 1111 1111
经过无符号右移后(<<<),使用0补位:
0111 1111 1111 1111 1111 1111 1111 1111
该值即2147483647(2^31)。以此类推,每移动一位,值就会减半,直到所有“1”都被移出,值变为0。
而byte类型变量-1的补码为:
1111 1111
因为是byte类型,所以在参与移位运算之前,会首先扩展为int类型,又因为byte是有符号类型,所以进行符号位扩展,如下:
1111 1111 1111 1111 1111 1111 1111 1111
然而,无符号右移后一位,使用0补位:
0111 1111 1111 1111 1111 1111 1111 1111
因为复合赋值运算符“>>>=”可以自动将结果转换为左侧操作数的类型,因此将结果转换为byte,这只需要进行简单的截断,即丢弃高24位,结果为:
1111 1111
因此,该值还是-1。只要是这样一位一位的移动,不管循环多少次,都是-1,也就是说结果是一个永远的“-1”。