Java移位运算符:<<,>>,>>>

最近在看jdk的源码,里面还是有很多地方用到了移位操作的,但是本人因为不怎么使用移位操作,基本是看了过一段时间就忘了,忘了用到的时候再看… …

所以在这里写下这篇博客,以后如果再忘记了直接翻回来看。


Java中有三种移位运算符,分别是左移运算符(<<),有符号右移(>>),无符号右移(>>>),作用分别如下:

  1. 左移运算符(<<):将数值的所有二进制位左移给定的位数,高位即左边的二进制位溢出则丢弃,低位空出来的部分补0
  2. 有符号右移(>>):将数值的所有二进制位右移给定的位数,高位空出来的补符号位,即负数补1,正数补0,;低位溢出的部分直接丢弃
  3. 无符号右移(>>>):将数值的所有二进制位右移给定的位数,高位空出来的补0,不论正数还是负数;低位溢出的部分直接丢弃

需要注意的是,移位的所有操作都是对数值的补码进行操作的!!!

我们知道数值有原码、反码、补码三种表示形式,在计算机里的整数值都是以补码的形式存储的。对于正数来说,原码=反码=补码;对于负数来说,反码等于原码除符号位之外各位取反,补码等于反码末位加一。

也就是说我们只需要关心负数的补码即可,并且负数原码反码补码中转换的规则是通用的,即原码=补码除符号位之外各位取反末位加一,而补码=原码除符号位之外各位取反末位加1。当然你也可以先减1然后除符号位之外各位取反。

补码:1000 0000 …这样的数很特殊,它是没有原码的。当数值为16位时,10000000 00000000的真值为-32768,32位时10000000 00000000 00000000 00000000的真值为-2147483648。

我们通过下面的代码来验证:

public class ShiftOptionsDemo {

    /**
     * 1(int):
     *   源码:00000000 00000000 00000000 00000001
     *   反码:与源码相同
     *   补码:与源码相同
     *
     * -1(int):
     *   源码:10000000 00000000 00000000 00000001
     *   反码:11111111 11111111 11111111 11111110
     *   补码:11111111 11111111 11111111 11111111
     *
     * << :左移
     * >> : 有符号右移
     * >>> : 无符号右移
     *
     * 左移会将补码的所有比特位向左移动
     * 有符号右移将所有比特位移动后“空出来”的比特位会补符号位,负数补1,正数补0
     * 无符号右移移动后“空出来”的比特位会补0,不管是正数还是负数
     *
     * 编译器(可能不是全部的编译器)发现移动的位数等于或超过数值本身的位数后,会
     * 对这个移动的位数进行取模操作, 也就是说一个int型的变量x,x << 32 等价于 x << 1
     */
    public static void main(String[] args) {

        /*
         * 结果:4
         *                 1补码
         * 00000000 00000000 00000000 00000001 = 1
         * 左移2位后的补码
         * 00000000 00000000 00000000 00000100 = 4
         */
        System.out.println(1 << 2);

        /*
         * 结果:-4
         *                -1补码
         * 11111111 11111111 11111111 11111111 = -1
         * 左移2位后的补码:
         * 11111111 11111111 11111111 11111100
         * 转换成原码:
         * 10000000 00000000 00000000 00000100 = -4
         */
        System.out.println(-1 << 2);

        /*
         * 结果:0
         *                  1补码
         * 00000000 00000000 00000000 00000001 = 1
         * 有符号右移2位后的补码:
         * 00000000 00000000 00000000 00000000 = 0
         */
        System.out.println(1 >> 2);

        /*
         * 结果:-1
         *                 -1补码
         * 11111111 11111111 11111111 11111111 = -1
         * 有符号右移2位后的补码:
         * 11111111111111111111111111111111111 = -1
         */
        System.out.println(-1 >> 2);

        /*
         * 结果:0
         *                  1补码
         * 00000000 00000000 00000000 00000001 = 1
         * 无符号右移2位后的补码:
         * 00000000 00000000 00000000 00000000 = 0
         */
        System.out.println(1 >>> 2);

        /*
         * 结果:1073741823
         *                 -1补码
         * 11111111 11111111 11111111 11111111 = -1
         * 无符号右移2位后的补码:
         * 00111111 11111111 11111111 11111111 = 1073741823
         * 这个时候负数就变成了正数
         */
        System.out.println(-1 >>> 2);

        //输出:1073741823
        System.out.println(0b00111111111111111111111111111111);

        /*
         * 11111111 11111111 11111111 11111111 = -1
         * 无符号右移一位后的结果:
         * 01111111 11111111 11111111 11111111 = 2147483647
         */
        System.out.println(-1 >>> 1);

        //输出:2147483647
        System.out.println(0b01111111111111111111111111111111);

    }
}

大多数情况下,左移1位相当于乘2,有符号右移相当于除2,但是也有很多特殊的数不符合。比如:

010000000 00000000 00000000 0000000本身是个正数,左移1位后就成负数了,并且就是1000 …这样的负数了。

-1有符号右移怎么移都是-1,因为-1的补码是:11111111 … 11111111,根据有符号右移的规则,-1的二进制补码再怎么移都还是它本身。