在平时看各类框架的源码的过程当中,常常会看到一些位移运算,因此做为一个Java开发者是必定掌握位移运算的。java
Java中有三个位移运算:微信
<<:左移
>>:右移
>>>:无符号右移
咱们直接看一下Demo:框架
System.out.println(2 << 1); // 4
System.out.println(2 >> 1); // 1
System.out.println(2 >>> 1); // 1
System.out.println(-2 << 1); // -4
System.out.println(-2 >> 1); // -1
System.out.println(-2 >>> 1); // 2147483647
复制代码
乍一眼看到上面Demo的打印结果,你应该是懵逼的,接下来我来解释一下这个结果究竟是如何运算出来的。学习
上面的Demo中有“2”和“-2”,这是两个十进制数,而且是int类型的(java中占四个字节),位运算是基于二进制bit来的,因此咱们须要将十进制转换为二进制以后再进行运算:spa
2 << 1
:十进制“2”转换成二进制为“00000000 00000000 00000000 00000010”,再将二进制左移一位,高位丢弃,低位补0,因此结果为“00000000 00000000 00000000 00000100”,换算成十进制则为“4”2 >> 1
:十进制“2”转换成二进制为“00000000 00000000 00000000 00000010”,再将二进制右移一位,低位丢弃,高位补0,因此结果为“00000000 00000000 00000000 00000001”,换算成十进制则为“1”对于这两种状况很是好理解,那什么是无符号右移,以及负数是怎么运算的呢?code
咱们先来看-2 << 1
与-2 >> 1
,这两个负数的左移与右移操做其实和正数相似,都是先将十进制数转换成二进制数,再将二进制数进行移动,因此如今的关键是负数如何用二进制数进行表示。cdn
接下来咱们主要介绍十进制数用二进制表示的不一样方法,因此为了简洁,咱们用一个字节,也就是8个bit来表示二进制数。开发
十进制 | 原码 |
---|---|
2 | 0000 0010 |
-2 | 1000 0010 |
原码实际上是最容易理解的,只不过须要利用二进制中的第一位来表示符号位,0表示正数,1表示负数,因此能够看到,一个数字用二进制原码表示的话,取值范围是-111 1111 ~ +111 1111
,换成十进制就是-127 ~ 127
。源码
在数学中咱们有加减乘除,而对于计算机来讲最好只有加法,这样计算机会更加简单高效,咱们知道在数学中5-3=2
,其实能够转换成5+(-3)=2
,这就表示减法能够用加法表示,而乘法是加法的累积,除法是减法的累积,因此在计算机中只要有加法就够了。数学
一个数字用原码表示是容易理解的,可是须要单独的一个bit来表示符号位。而且在进行加法时,计算机须要先识别某个二进制原码是正数仍是负数,识别出来以后再进行相应的运算。这样效率不高,能不能让计算机在进行运算时不用去管符号位,也就是说让符号位也参与运算,这就要用到反码。
十进制 | 原码 | 反码 |
---|---|---|
2 | 0000 0010 | 0000 0010 |
-2 | 1000 0010 | 1111 1101 |
正数的反码和原码同样,负数的反码就是在原码的基础上符号位保持不变,其余位取反。
那么咱们来看一下,用反码直接运算会是什么状况,咱们以5-3
举例。
5 - 3
等于 5 + (-3)
十进制 | 原码 | 反码 |
---|---|---|
5 | 0000 0101 | 0000 0101 |
-3 | 1000 0011 | 1111 1100 |
5-3
= 5+(-3)
= 0000 0101(反码) + 1111 1100(反码)
= 0000 0001(反码)
= 0000 0001(原码)
= 1
复制代码
这不对呀?!! 5-3=1?,为何差了1?
咱们来看一个特殊的运算:
1-1
= 1+(-1)
= 0000 0001(反码) + 1111 1110(反码)
= 1111 1111(反码)
= 1000 0000(原码)
= -0
复制代码
咱们来看一个特殊的运算:
0+0
= 0000 0000(反码) + 0000 0000(反码)
= 0000 0000(反码)
= 0000 0000(原码)
= 0
复制代码
咱们能够看到1000 0000表示-0,0000 0000表示0,虽然-0和0是同样的,可是在用原码和反码表示时是不一样的,咱们能够理解为在用一个字节表示数字取值范围时,这些数字中多了一个-0,因此致使咱们在用反码直接运算时符号位能够直接参加运算,可是结果会不对。
为了解决反码的问题就出现了补码。
十进制 | 原码 | 反码 | 补码 |
---|---|---|---|
2 | 0000 0010 | 0000 0010 | 0000 0010 |
-2 | 1000 0010 | 1111 1101 | 1111 1110 |
正数的补码和原码、反码同样,负数的补码就是反码+1。
十进制 | 原码 | 反码 | 补码 |
---|---|---|---|
5 | 0000 0101 | 0000 0101 | 0000 0101 |
-3 | 1000 0011 | 1111 1100 | 1111 1101 |
5-3
= 5+(-3)
= 0000 0101(补码) + 1111 1101(补码)
= 0000 0010(补码)
= 0000 0010(原码)
= 2
复制代码
5-3=2!!正确。
再来看特殊的:
1-1
= 1+(-1)
= 0000 0001(补码) + 1111 1111(补码)
= 0000 0000(补码)
= 0000 0000(原码)
= 0
复制代码
1-1=0!!正确
再来看一个特殊的运算:
0+0
= 0000 0000(补码) + 0000 0000(补码)
= 0000 0000(补码)
= 0000 0000(原码)
= 0
复制代码
0+0=0!!也正确。
因此,咱们能够看到补码解决了反码的问题。
因此对于数字,咱们可使用补码的形式来进行二进制表示。
咱们再来看-2 << 1
与-2 >> 1
。
-2用原码表示为10000000 00000000 00000000 00000010
-2用反码表示为11111111 11111111 11111111 11111101
-2用补码表示为11111111 11111111 11111111 11111110
-2 << 1
,表示-2的补码左移一位后为11111111 11111111 11111111 11111100
,该补码对应的反码为
11111111 11111111 11111111 11111100
- 1
= 11111111 11111111 11111111 11111011
复制代码
该反码对应的原码为:符号位不变,其余位取反,为10000000 00000000 00000000 00000100
,表示-4。
因此-2 << 1 = -4
。
同理-2 >> 1
是同样的计算方法,这里就不演示了。
上面在进行左移和右移时,我有一点没讲到,就是在对补码进行移动时,符号位是固定不动的,而无符号右移是指在进行移动时,符号位也会跟着一块儿移动。 好比-2 >>> 1
。
-2用原码表示为10000000 00000000 00000000 00000010
-2用反码表示为11111111 11111111 11111111 11111101
-2用补码表示为11111111 11111111 11111111 11111110
-2的补码右移1位为:01111111 11111111 11111111 11111111
右移后的补码对应的反码、原码为:01111111 11111111 11111111 11111111
(由于如今的符号位为0,表示正数,正数的原、反、补码都相同)
因此,对应的十进制为2147483647。
也就是-2 >>> 1 = 2147483647
文章写的可能比较乱,但愿你们能看懂,能有所收获。这里总结一下,咱们能够发现:
2 << 1 = 4 = 2*2
2 << 2 = 8 = 2*2*2
2 << n = 2 * (2的n次方)
m << n = m * (2的n次方)
右移则相反,因此你们之后在源码中再看到位运算时,能够参考上面的公式。
若是想第一时间学习更多的精彩的内容,请关注微信公众号:1点25