在平时看各种框架的源码的过程中,经常会看到一些位移运算,所以作为一个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来的,所以我们需要将十进制转换为二进制之后再进行运算:
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”
对于这两种情况非常好理解,那什么是无符号右移,以及负数是怎么运算的呢?
我们先来看-2 << 1与-2 >> 1,这两个负数的左移与右移操作其实和正数类似,都是先将十进制数转换成二进制数,再将二进制数进行移动,所以现在的关键是负数如何用二进制数进行表示。
原码、反码、补码杰西莱我们主要介绍十进制数用二进制表示的不同方法,所以为了简洁,我们用一个字节,也就是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!!也正确。
所以,我们可以看到补码解决了反码的问题。
所以对于数字,我们可以使用补码的形式来进行二进制表示。
负数位移运算