Java 的移位运算符有三种(引用《Java编程思想(Bruce Eckel, 第4版)》):
- “<<” :左移运算符,按照操作符右侧指定的位数将操作符左边的操作数向左移动(在低位补0);
- “>>” :右移运算符,按照操作符右侧指定的位数将操作符左边的操作数向右移动;
“有符号”右移操作符使用“符号扩展”:- 若符号为正,则在高位插入0;
- 若符号为负,则在高位插入1。
- “>>>”:“无符号”右移操作符,它使用“符号扩展”:无论正负都在高位插入0。
- 没有无符号左移
这些解释并没有说清楚原理,这里的移位运算符其实是对补码进行操作。
这里需要先回顾一下原码、反码、补码的概念(以32-bit为例,第一位为符号位):
- 正数: 正数的反码、补码都等于原码,如3,原码、反码、补码都是 0000 0000 0000 0000 0000 0000 0000 0011;
- 负数:以-3为例
- 原码:1000 0000 0000 0000 0000 0000 0000 0011;
- 反码:1111 1111 1111 1111 1111 1111 1111 1100;(符号位不变,其余位取反)
- 补码:1111 1111 1111 1111 1111 1111 1111 1101;(符号位不变,其余位取反后+1)
下面是几个例子:
- 正数
int a = 3;
System.out.println(a << 1); // 输出: 6
System.out.println(a >> 1); // 输出: 1
正数的补码跟原码一样,所以3左移一位即:
0000 0000 0000 0000 0000 0000 0000 0011 ==> 0000 0000 0000 0000 0000 0000 0000 0110
即 3 ==> 6 (10进制);
3右移一位:
0000 0000 0000 0000 0000 0000 0000 0011 ==> 0000 0000 0000 0000 0000 0000 0000 0001
即 3 ==> 1 (10进制);
- 负数:
int b = -3;
System.out.println(b << 1); // 输出: -6
System.out.println(b >> 1); // 输出: -2
-3的补码是 1111 1111 1111 1111 1111 1111 1111 1101;
-3左移一位:
补码变为:1111 1111 1111 1111 1111 1111 1111 1010;
对补码再取补,得到对应的原码。
对应原码:1000 0000 0000 0000 0000 0000 0000 0110;
即 -3 ==> -6 (10进制);
-3右移一位:
补码变为:1111 1111 1111 1111 1111 1111 1111 1110;
对补码再取补,得到对应的原码。
对应原码:1000 0000 0000 0000 0000 0000 0000 0010;
即 -3 ==> -2 (10进制);
- 无符号右移
int a = 3;
int b = -3;
System.out.println(a >>> 1); // 输出: 1
System.out.println(b >>> 1); // 输出: 2147483646
无符号右移其实就是将符号位也当成一个普通的位,因为正数的符号位为0,所以对正数来说,无符号右移跟有符号右移是一样的。而对负数来说,无符号右移是在高位插入0,而有符号右移是在高位插入0。
-
-3无符号右移1位过程:
- 原码:1000 0000 0000 0000 0000 0000 0000 0011;
- 补码:1111 1111 1111 1111 1111 1111 1111 1101;
- 无符号右移:0111 1111 1111 1111 1111 1111 1111 1110;
- 再求补转原码:0111 1111 1111 1111 1111 1111 1111 1110;
注意: 此时在转换成10进制时,仍认为最高位为符号位。而右移后最高位为0,即正数,而对正数再求补仍是这个数。
即 -3 ==> 2147483646 (10进制)。