总结一波Java的位运算符

一般来说位运算符只能操作整数类型的 变量或者值。

Java支持的位运算符有以下7个。

●  &:按位与。当两位同时 为1时才返回1。

●  | :按位或。只要有一位为1即可返回1。

● ~ :按位非。单目运算符,将操作数的每个位(包括符号位)全部取反。

● ^ :按位异或。当两位相同时返回0,不同时返回1。

● << :左移运算符。

● >> :右移运算符。

● >>> : 无符号右移运算符。

1. 位运算符的运算规则如下表所示

位运算符的运算规则
第一个数 第二个数 按位与 按位或 按位异或
0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 1 0

注意: 按位非只需要一个操作数,该运算符把操作数的二进制码按位(包括符号位)取反。

下面我们来分享一下按位与和按位或的运算原理。首先看两句代码:

System.out.println("按位与:" + (5 & 9));
System.out.println("按位或:" + (5 | 9));

这两句代码的运行结果是“按位与:”+1;“按位或:”+13 

这里要分清楚&&和&以及| 和||的区别。&&和||是逻辑运算符,而&和|是位运算符!!!位运算符!!!位运算符!!!(重要的事情说三遍)。所以毋庸置疑对于这两个运算符我们需要将操作数切换成二进码制然后再进行运算。

现在我们来分析一下上面两句代码的运算过程:

首先:5&9:

5的二进制码是:00000101(前面还有24个0)

9的二进制码是:00001001(同样前面有24个0)

所以:5&9和5 | 9 运算过程如图

                          

所以将所得结果换算成十进制就分别变成了1和13了。 

2. 按位非和按位异或

上面讲了按位与和按位或的运算原理,现在们来看一下按位非和按位异或的运算原理。

同样先看两句代码:

System.out.println("按位非:" + (~-5));
System.out.println("按位异或:" + (5 ^ 9));

其运行结果是:按位非:4;按位异或:12。

这里先普及一下二进制码有原码、反码和补码之说。

(1)二进制的最高位(最左边的位)是符号位:0表示正数,1表示负数

(2)正数的原码、反码和补码都是一样的

(3)负数的原码为该数对应的无符号数的二进制将符号位置为1

(4)负数的反码为该数原码的符号位不变,其他位取反(1变成0;0变成1)

(5) 负数的补码为该数的反码加1

(6)0的反码补码都是0

(7)计算机中,都是以补码的形式来运算的

a)了解了负数三码(原码、反码和补码的简称)的由来,现在我们再来分析(~-5)的运算过程。

-5的原码:

1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1

-5的反码(原码符号位不变,其他位取反):

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0

-5的补码(反码加1):

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1

所以(~-5)的二进制为(非就是将所有为取反)

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0

将上面的二进制码换算成十进制就是4。

负数由反码得到补码的过程(加1)我没有详细给出,二进制的加法比较简单,也就类似于十进制的加法,不过是逢二进一。

所以这里不再啰嗦,想必大家都会,如有不明白的可以参考一下这篇文章。本文所写的普及知识那几条也参考了这篇文章。

写的不错,我先感谢一下作者。

b)现在我们来分析一下按位异或(5^9)的运算过程

再提一嘴按位异或的运算规则是:两位相同时返回0,不同时返回1

下面分析过程:

5的二进制码(三码合一):

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1

9的二进制码:

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1

现在我们按照异或的规则进行计算

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0

将运算结果换算成十进制就是12。

3. 移位运算符(>>(右移)、<<(左移)和>>>(无符号右移))

 上边分析的那些是比较简单的也是大家都比较熟悉的。 接下来要讲解的内容将是本文的重点,仔细分析一下并不算难,但是我确确实实是刚弄明白没多久呢。之前没有认真分析过总是道听途说可以认为成<< (左移)就是乘以2的多少多少次方,>>(右移)就是取整2的多少多少次方。至于>>>(无符号右移)那是更加的不知道。然而我并没有想过负数进行移位运算该怎么办。我发现我作为一个写代码的真的挺不合格的,之前都是一味的功能罗列、堆叠,这里抄点那里抄点的。 以至于到现在才弄明白移位运算符的运算原理果真无比汗颜。。。

废话不多说了现在我就把我所理解的移位运算写一写,至少能帮助自己在以后温习的时候方便一些。当然如果能帮到大家那我将非常欣慰,所以热烈欢迎大家能留言指出我的不足。

a)现在看一下左移运算的运算过程,照样先看两句代码:

System.out.println("左移运算:" + (5 << 2));
System.out.println("左移运算:" + (-5 << 2));

这两句代码的运行结果是:20、-20。

得知结果之后心里先默念两遍左移运算的规则,将操作数的二进制码整体左移指定位数,左移后右边空出来的位以0补充

过程分析:(以-5为例)

-5的补码:

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1

下面进行左移操作:

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0 0

其中绿色字体的(前两位)是补码经过左移操作后被移出去的两位;红色字体的是原来补码中有的,蓝色字体(最后两位)的是左移之后缺少的两位由0填补上的。

由上可知,该二进制码最高位(红色字体最左边一位)是1,所以负数左移运算之后仍是负数。

注意:负数左移之后得到的二进制码仍旧是补码,换算成十进制时仍需要先将补码换成原码。

上面我们介绍了负数二进制补码的由来,这样我们倒着推就可以由补码得到原码,也就是补码减1得到的二进制码再取反(反码的首位为1不变)。不过我自己在分析的时候,负数由补码换算原码我不是这样推算的,所以在这儿我也不按照这个倒推的方式来分析,现在看另一种方法。

负数二进制求十进制可以先将补码取反然后再加1,然后再转成十进制所得的数字就是该负数的绝对值。

两种方式应该是比较相似的,并且也比较简单,所以这里不再啰嗦。大家可自行推算。

b)分析完左移我们再来看一下右移运算的过程

二话不说再接着上代码

System.out.println("右移运算:" + (9 >> 3));
System.out.println("无符号右移运算:" + (9 >>> 3));
System.out.println("右移运算:" + (-9 >> 2));
System.out.println("无符号右移运算:" + (-9 >>> 2));

这次我们换个操作数不用5和-5了。

这几句代码的运行结果是1、1、-3和1073741821。还是一样的在分析运算原理之前先介绍一下右移运算符和无符号右移运算符的规则。

右移运算符(>>)运算规则:把第一个操作数(这里就是9和-9)的二进制码右移指定位数(分别是3位和两位)后,左边空出来的位以原来的符号位填充,即如果第一个操作数是正数,则左边补0;如果第一个操作数是负数,则左边补1。

无符号右移运算符(>>>)运算规则:把第一个操作数的二进制码右移指定位数后,无论是正数还是负数左边空出来的位总是以0补充。

下面针对上面的几句代码我们类分别分析其运算过程。 

1)9>>3:

9的二进制码(三码合一):

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1

向右移三位后的结果:

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1

其中蓝色字体(前三位)是右移之后弥补的左边的空缺,红色字体是原来9的二进制码中含有的,绿色字体(最低三位)是右移移除的三位。所以9>>3运算其结果的二进制码就是蓝色和红色字体组成的二进制码。我觉得只看这个二进制码不用分析我们也能知道结果是什么了吧。

2)9>>>3:

根据右移运算符和无符号右移运算符的运算规则我们可以知道,(>>)和(>>>) 的区别就在于右移之后左边的空缺位补充的是什么。对于正数而言无论这两种运算符中的哪一种其运算结果和过程都是一致的。

3)-9>>2:

-9的二进制补码:

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1

向右移两位的结果是:

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1

其中蓝色字体(前两位)是右移之后弥补的左边的空位,红色字体是原来-9的二进制补码中含有的,绿色字体(最右边两位)是右移移除的两位。所以-9>>2运算其结果的二进制码就是蓝色和红色字体组成的二进制补码。再根据上边介绍的负数由补码计算原码的方法来计算出原码。

补码取反:

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0

反码加1得原码:

1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

得到原码之后转换成十进制就是3,注意首位是1,所以这个数得是个负数,上面也提到了按这个方法得到的十进制是原来负数换算成十进制的绝对值,所以-9>>2(右移两位)的运算结果是-3。

4)-9>>>2:

-9的二进制码上边给出了,根据无符号右移运算的规则可知,-9>>>2运算结果的二进制码是:

0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1

同样最后两位绿色字体的是右移之后右边移除的两位。跟-9>>2的不同就在于,右移两位之后左边空余的两位补位补谁的问题。无符号运算法则我们已然知道,-9是负数所以这里需要补两个0。看到这个二进制码,首先我们不管什么运算规则和运算过程,至少我们能知道两点:

第一:结果一定是一个正数

第二:结果值一定不小。因为这个二进制码高两位是0,其他位中只有一个0其他都是1。

这个二进制码换算成十进制就是1+2²+2³+...+2的29次方,所得的结果就是一个比较大的数字1073741821。

通过上面的分析,我还是验证了我的那个道听途说<<(左移运算符),确实是相当于用操作数乘以2的所左移位数次方,无论操作数是正数还是负数都成立;对于>>(右移运算符)相当于用操作数取整2的所右移位数次方,在这里正数和负数就有区别了。正数做右移操作,不论该操作数取整2的右移位数次方有余数与否都只取取整的结果不与余数做任何操作;负数做右移操作,如果该操作数取整2的右移位数次方有余数则需要将商再与余数做相加的操作。

至于无符号右移运算符(>>>),正数做该操作与正数做右移运算操作所得结果是一致的,这一点如果不明白那再去仔细看一看右移运算符和无符号右移运算符的运算规则就行了。负数做该操作首先一定会是一个很大的正数,这个大家去举个例子分析一下估计也就能明白了。如果大家觉得我所总结的这些结论有偶然性那大家可以再自行举例验证,我就不再举例验证了。

本文到这里也就该结束了,篇幅不算长但用的时间不短了。欢迎大家评论留言。

拜拜6.。。。 

猜你喜欢

转载自blog.csdn.net/zhourui_1021/article/details/103266155