JAVA中有趣的移位操作

<<, >>, <<<, >>> 这些符号什么意思?有哪些容易被遗漏的细节?

上次介绍了JAVA中有趣的位运算,知道了位运算是直接对一个整形的二进制位进行操作,效率上比起加减乘除高不少,因此常运用在对性能很敏感的场景。

今天介绍在二进制下的移位操作。

原码、反码、补码

磨刀不误砍柴工,这几个名词可还有印象?

  • 原码: 二进制表示,最左边的一位是符号位,0表示正数,1表示负数

  • 反码: 正数时同原码,负数时,等于原码每位取反(除了符号位)

  • 补码: 正数时同原码,负数时,等于反码+1

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

JAVA中也一样,存储和移位操作的都是补码,正数时都一样,负数时就要注意了。

<< 左移位

在二进制格式下,把所有的数字向左移动指定位数,左边的高位移出(舍弃),右边的低位多出来的空位补0。

n = n << 1,左移一位,相当于 n = n * 2

需要注意的是,正数的二进制最高位是0,如果左移后被怼上来的那位是1,这个数就成了负数。

如果觉得奇怪,想想有时候我们遇到过的场景:一个很大的int正数,乘一个正数后如果结果超过了int能存储的极限,往往就变成了负数,或者一个很小的正数。

另一个需要注意的地方,由于Java只存储补码,正数补码和原码相同先不管,负数的补码会把原码的0变成1,所以负数左移位时,移出去的最高是1,后面怼上来的一般也是1(没到极限),所以还是负数。

对于程序员,或许还是把内容和代码放在一起更容易让人注意...
public class Bit {	
    public static void main(String[] args) {	
        int n = 6;                                   // ..000110 (6)	
        System.out.println(n + "左移1位 " + (n &lt;&lt; 1));// ..001100 (12)	
        System.out.println(n + "左移2位 " + (n &lt;&lt; 2));// ..011000 (24)	
        System.out.println(n + "左移3位 " + (n &lt;&lt; 3));// ..110000 (48)	
        // 通过2进制的方式赋值(jdk7+),0b开头,后面是二进制表示(超过int最大值就要结尾加L成为Long)	
        // 等同于int x = 1073741825	
        // 01000000000000000000000000000001 第一位0表示正数	
        int x = 0b01000000000000000000000000000001;	
        System.out.println("x = " + x);	
        // 10000000000000000000000000000010	
        // 左移后最高位成了1,变成了负数	
        System.out.println("x左移1位\t= " + (x &lt;&lt; 1));	
        // 就像乘2如果超过了int最大值也会变成负数,结果是一样一样的	
        System.out.println("x乘2\t= " + (x * 2));	
        // 如果左移两位,最高位依然是正数(4)	
        // 00000000000000000000000000000100	
        System.out.println("x左移2位\t= " + (x &lt;&lt; 2));	
        // 正数超过极限可能变成负数,负数变成正数也不奇怪吧	
        System.out.println("x乘4\t= " + (x * 2 * 2));	
        // y = -3 时:	
        // 原码: 10000000000000000000000000000011	
        // 反码: 11111111111111111111111111111100 (符号位除外,其余取反)	
        // 补码: 11111111111111111111111111111101 (反码+1)	
        // Java存储补码,移位操作也是对补码操作	
        // 也就解释了为啥负数时左移1位也能和乘2结果一样(最高位的1没了,后面跟上来的也是1,符号还是负)	
        int y = -3;	
        System.out.println(y + " 二进制表示(补码) " + Integer.toBinaryString(y));	
        //补码左移一位后: 11111111111111111111111111111010	
        //转为反码:      11111111111111111111111111111001  (补码-1)	
        //转为原码:      10000000000000000000000000000110	
        //十进制: -6	
        System.out.println(y + " 左移1位 " + (y &lt;&lt; 1));	
    }	
}	
/* 输出:	
6左移1位 12	
6左移2位 24	
6左移3位 48	
x = 1073741825	
x左移1位    = -2147483646	
x乘2       = -2147483646	
x左移2位    = 4	
x乘4       = 4	
-3 二进制表示(补码) 11111111111111111111111111111101	
-3 左移1位 -6	
*/

>> 右移位

在二进制格式下,把所有的数字向右移动指定位数,低位移出(舍弃),高位的空位补符号位,即正数补0,负数补1(想想负数存的补码和原码是不同的)。

n = n >> 1,右移一位,相当于 n = n / 2 (PS:正数时)

public class Bit {	
    public static void main(String[] args) {	
        int n = 6;                                   // ..110 (6)	
        System.out.println(n + "右移1位 " + (n &gt;&gt; 1));// ..011 (3)	
        System.out.println(n + "右移2位 " + (n &gt;&gt; 2));// ..001 (1)	
        System.out.println(n + "右移3位 " + (n &gt;&gt; 3));// ..000 (0)	
        System.out.println(n + "右移4位 " + (n &gt;&gt; 4));// ..000 (0)	
    }	
}	
/* 输出:	
6右移1位 3	
6右移2位 1	
6右移3位 0	
6右移4位 0	
*/

上面是正数右移,负数的时候情况又有点不同了。

640?wx_fmt=jpeg

由于计算机存储和位移的都是补码,正数补码和原码一样,一直右移最后都变成了0,就像一直整除2,最后不管怎么除都是0。

而负数的补码一直右移最后全都是1,即:

补码:   11111111111111111111111111111111	
反码:   11111111111111111111111111111110 (补码-1)	
原码:   10000000000000000000000000000001	
十进制: -1
public class Bit {	
    public static void main(String[] args) {	
        int m = -3;	
        System.out.println(m + "\t补码 " + Integer.toBinaryString(m));	
        System.out.println(m + "\t补码右移1位 " + (m &gt;&gt; 1));	
        System.out.println(m + "\t补码右移2位 " + (m &gt;&gt; 2));	
        System.out.println(m + "\t补码右移3位 " + (m &gt;&gt; 3));	
    }	
}	
/* 输出:	
-3    补码 11111111111111111111111111111101	
-3    补码右移1位 -2	
-3    补码右移2位 -1	
-3    补码右移3位 -1	
*/

>>> 无符号右移

依然是右移指定位数,与右移不同的是,无论正负,高位均补0。对于正数没影响,对于负数来说,这样一移,直接变成正数了。

public class Bit {	
    public static void main(String[] args) {	
        int m = -3;	
        System.out.println(m + "\t补码 " + Integer.toBinaryString(m));	
        System.out.println(m + "\t补码无符号右移1位 " + (m &gt;&gt;&gt; 1));	
        System.out.println(m + "\t补码无符号右移2位 " + (m &gt;&gt;&gt; 2));	
        System.out.println(m + "\t补码无符号右移3位 " + (m &gt;&gt;&gt; 3));	
    }	
}	
/* 输出:	
-3    补码 11111111111111111111111111111101	
-3    补码无符号右移1位 2147483646	
-3    补码无符号右移2位 1073741823	
-3    补码无符号右移3位 536870911	
*/

<<< 无符号左移

640?wx_fmt=jpeg

640?wx_fmt=jpeg

位数限制

一个容易忽略的地方,每次移动一位循环N次,和一次移动N位,结果并不一定是一样的。

以int为例,如果直接左移36位,结果并不是0,而是等同于左移36%32=4位。

右移和无符号右移也同样适用。

public class Bit {	
    public static void main(String[] args) {	
        int m = 3;	
        int t = m;	
        for (int i = 1; i &lt;= 36; i++) {	
            t = t &lt;&lt; 1;	
        }	
        System.out.println(t);	
        System.out.println(m &lt;&lt; 36);	
        System.out.println(m &lt;&lt; (36 % 32));	
    }	
}	
/* 输出:	
0	
48	
48	
*/

总结

  1. 箭头朝哪边,就往哪边移位

  2. 左移操作相当于乘2,右移相当于除2,不全是

  3. 左移操作可能改变正负,因为符号位会被移走,新符号位不一定和以前一样

  4. 右移操作不改变符号,因为左边填充的是符号位

  5. 无符号右移会把负数变成正数

  6. 没有无符号左移

  7. 位移超过JAVA基本类型的位数后,等同于位移取模后的位数

推荐阅读


JAVA中有趣的位运算

探索JAVA并发 - 并发容器全家福

探索JAVA并发 - 终于搞懂了sleep/wait/notify/notifyAll

探索JAVA并发 - 如何减少锁的竞争

探索JAVA并发 - 如何优雅地取消线程任务

640?wx_fmt=jpeg

关注、转发、在看都是对作者最大的肯定?

  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值