Java 中的位移运算符 >> , << , >>>

2,494 阅读4分钟

最近在看一些在学习一些源码,在源码中经常会看到一些位移运算符的使用,比如在看HashMap源码的时候,在计算hashMap容量的阈值的时候,就使用到了位移运算符,代码如下:

static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

对位移运算符概念不熟悉的同学,看到这个估计是一脸懵逼,所以要想完全看懂源码,就需要对位移运算符有一定的了解。下面我用以任意一个10进制的int 数据 int e = 12345 为例进行解析:

12345 二进制表达:

12345二进制表达.png

###1、左位移运算符 <<

如果 e << 1 ,左位移1位:

左位移1位1.png

位移后十进制数值变成:24690,刚好是12345的二倍,所以有些人会用左位移运算符代替乘2的操作,但是这并不代表是真的就是乘以2,很多时候,我们可以这样使用,但是一定要知道,位移运算符很多时候可以代替乘2操作,但是这个并不代表两者是一样的(这一点需要格外注意,很多人都存在这样的误解),接着往下看:

如果继续左移,12345左移14位: e<<14

左位移14位.png
这里要注意了,左位移18位后,二进制首位为1,如下图所示:
左位移18位.png

此时二进制表达首位为1,此时数值为 -1058799616,同理,如果继续位移,左位移20位,则值为 59768832 又变成了正数

所以根据这个规则,如果任意一个十进制的数左位移32位,右边补位32个0,十进制岂不是都是0了?当然不是!!! 当int 类型的数据进行左移的时候,当左移的位数大于等于32位的时候,位数会先求余数,然后再进行左移,也就是说,如果真的左移32位 e << 32 的时候,会先进行位数求余数,即为 e<<(32%32) 相当于 e<< 0 ,所以e<< 33 的值和e<<1 是一样的,都是 24690

2、右移运算符 >>

同样,还是以12345这个数值为例, e右移1位: e>>1

12345右移1位.png
右移后得到的值为 6172 和int 类型的数据12345除以2取整所得的值一样,所以有些时候也会被用来替代除2操作

但是如果继续右移,直接右移14位,即为e>>14,则结果为0 ,其过程和左移相似,就不一一演示了;另外,对于超过32位的位移,和左移运算符一样,,会先进行位数求余数

3、无符号右移运算符:>>>

无符号右移运算符和右移运算符是一样的,不过无符号右移运算符在右移的时候是补0的,而右移运算符是补符号位的

一下是 -12345 二进制表达式

-12345二进制表达.png

对于源码、反码、补码不熟悉的同学,请自行学习,这里就不再进行补充了讲解了,这里提醒一下,在右移运算符中,右移后补0,是由于正数 12345 符号位为0 ,如果为1 则应补1

无符号右移运算符.png

最后补充一下:

1、原码、反码和补码

一个数可以分成符号位(0正1负)+ 真值,原码是我们正常想法写出来的二进制。由于计算机只能做加法,负数用单纯的二进制原码书写会出错,于是大家发明了反码(正数不变,负数符号位不变,真值部分取反);再后来由于+0, -0的争端,于是改进反码,变成补码(正数不变,负数符号位不变,真值部分取反,然后+1)。二进制前面的0都可以省略,所以总结来说:计算机里的负数都是用补码(符号位1,真值部分取反+1)表示的。

2、位运算符和2的关系

位运算符和乘2 除2 在大多数时候是很相似的,可以进行替代,同时效率也会高的多,但是两者切记不能混淆 ; 很多时候有人会把两者的概念混淆,尤其是数据刚好是 2、4、6、8、100等偶数的时候,看起来就更相似了,但是对于奇数,如本文使用的12345 ,右移之后结果为6172 ,这个结果就和数学意义上的除以2不同了,不过对于int 类型的数据,除2 会对结果进行取整,所以结果也是6172 ,这就更有迷惑性了