不少编程语言都有位运算符,Java语言也不例外。在Java语言中,提供了7种位运算符,分别是按位与(&)、按位或(|)、按位异或(^)、取反(~)、左移(<<)、带符号右移(>>)和无符号右移(>>>)。这些运算符当中,仅有~是单目运算符,其余运算符均为双目运算符。在讲解这些运算符的使用以前,必须了解一个常识,那就是:位运算符是对long、int、short、byte和char这5种类型的数据进行运算的,咱们不能对double、float和boolean进行位运算操做。下面就来详细讲解这7种位运算符的使用方法。面试
按位与运算符的写法是一个”&”符号,与”不短路的逻辑与运算符”写法是彻底同样的,但意义不一样。逻辑与运算是对布尔型数据进行运算,而按位与运算是对二进制位上的数值进行计算。按位与运算符的运算规则以下图所示:
运算规则总结成一句话就是:若是两个二进制位上的数都是1,那么运算结果为1,其余状况运算结果均为0。下面举例说明按位与运算符的运算过程,咱们用数字5和6进行按位与运算。这个过程能够用下图表示:
运算过程当中,首先把5和6这两个数字转换为补码,以后还要把这两个数字按位对齐,而后一一把两个相应的二进制位上的数字进行按位与运算,运算获得的二进制串就是最终的结果。按照补码反向转换为十进制数字的规则,能够计算出5&6的运算结果是4。在这里要提醒你们一句:进行位运算的时候,最左边的符号位也是要参与运算的。编程
按位或运算符的写法是一个”|”符号,与”不短路的逻辑或运算符”写法相同,它的运算规则也很简单,以下图所示:编程语言
运算规则归纳成比较好记的一句话就是:两个二进制位上的数字若是都为0,那么运算结果为0,不然运算结果是1。同按位与运算同样,符号位也要参与运算。下面咱们仍是用5和6为例来说解一下按位或的运算过程,以下图所示:ide
首先仍是把这两个数字转换成补码形式,以后把相应的二进制位上的数字进行按位或运算:若是两个二进制数都是0,计算结果为0,其余状况计算结果均为1。按照这个规则把每一位上的数字都计算一遍后,获得二进制的运算结果是111,这个运算结果转换为十进制数是7。学习
按位异或运算符写法是”^”,它的运算规则以下图:加密
如上图,运算规则为:两个二进制位上的数字若是相同,则运算结果为0,若是两个二进制位上的数字不相同,则运算结果为1。下面咱们仍是用5和6为例来说解一下异或的运算过程,以下图:
首先仍是把这两个数字转换成补码形式,以后把相应的二进制位上的数字进行异或运算,若是对应的两个二进制位上的数相同,计算结果为0,不然计算结果为1。按照这个规则把每一位上的数字都计算一遍后,获得二进制的运算结果是11,这个运算结果转换为十进制数是3。
关于异或运算符,有不少很是有用的特性,咱们在这里梳理总结一下。
Ⅰ、异或运算符知足交换律
也就是说,a^b与b^a是等价的,虽然a和b交换了位置,但仍是会运算出相同的结果。这个规律还能够推广到N个操做数,也就是说,若是有N个变量都参与了异或运算,那么它们的位置不管如何交换,运算的结果都是相同的。设计
Ⅱ、任何两个相同的数字进行异或操做,所获得的结果都必然为0
这个特性并不难理解,由于两个相同的数字,换算成补码后,每一个二进制位上的数也都相同,这样在进行异或运算时,按照运算规则,每一个二进制位上获得的运算结果也都是0,这N个0所组成的二进制串就是数字0的补码。咱们能够利用这个特性快速的判断两个整数是否相同。另外,利用这个特性还能够实现内存的快速清零操做,好比咱们能够在代码中写上a=a^a;这条语句能快速的把变量a所占据的那几个字节的内存迅速清零。3d
Ⅲ、对于任意一个二进制位来讲,这个位上的数与0进行异或运算,运算结果与这个二进制位上的数是相同的,而与1进行异或运算,结果与这个二进制位上的数字相反
注意,咱们如今说的是二进制位上的数字,所谓相反不是说原来这个位上是1,运算结果是-1,而是说原来是1,运算结果为0,原来若是是0,运算结果是1,这才是此处所说的”相反”的概念。这个特性也很是好理解,小伙伴们必定要记住它,在之后进行一些位运算操做的时候常常会用到这个特性。
Ⅳ、对于任何两个整数a和b,a^b^b等于a
这个结论为何成立呢?简单说来,就是由于这个表达式中有b^b,而b^b的结果为0,前文已讲过,任何一个数与0进行按位异或操做,结果仍然是这个数自己,因此,a^b^b等于a。这个特性在加密运算方面有着很广泛的应用。咱们能够把a看成要加密的数据,而把b看成密钥。a异或b就是把a用密钥b进行了加密操做,当须要解密时,仍然以b做为密钥,再进行一次异或就实现了解密。
这个特性还能够推出另一个结论:对于任何两个整数a和b,a^b^a等于b。咱们可以获得这个结论的缘由也很简单,就是由于按照交换律,a^b与b^a的运算结果是同样的,因此a^b^a等价于b^a^a,这个表达式中出现了a^a,a^a的值也为0,因此整个表达式的其实就至关于b^0,最终结果仍是b。
但愿你们可以牢记以上这些结论,在后续的文章中,会讲解如何用这些结论去解决实际问题。视频
按位取反运算符写法是”~”,它的运算规则是:对每一个二进制位进行取反操做,所谓取反就是原来二进制位上若是是0,那么就变成1,反之,若是原来二进制位上是1,那么就变为0。取反运算符是一个单目运算符,因此只须要一个操做数就能够了。咱们以数字5为例讲解按位取反的运算过程:
首先把数字5转换成补码形式,以后把每一个二进制位上的数字进行取反,若是是0就变成1,若是1就变成0,通过取反后获得的二进制串就是运算结果,这个运算结果被还原为十进制数是-6。取反运算符的运算规则也很是容易理解,可是在这里老师须要提醒各位读者注意:若是是对变量进行取反操做,那么通过操做以后,变量的值并不会发生变化!为方便小伙伴们理解,请看下图:
从程序运行的结果能够看出:输出a的值仍是5,这说明变量a通过取反获得的那个-6并无被赋值到变量a中,经过这个例子能够证实,取反运算并无对变量从新赋值的功能,取反运算的结果只是临时保存在操做数栈中,变量自己的值不会因取反操做而发生改变。blog
下面再来说解一下与位移相关的运算符。所谓”位移”就是指在内存中对二进制串进行移动的操做。只要是移动操做,就必然会涉及到如下几个问题,怎样表示移动方向?怎样表示移动的位数?移动以后空出来的二进制位用什么来填充?移动以后跑到原来内存单元外面的那些数字怎么处理?符号位要不要跟着一块儿移动?这些问题都是咱们学习位移操做要弄清楚的细节。各位小伙伴能够带着这些问题来学习位移相关的运算符。与位移相关的运算符有三个,分别是左移(<<)、带符号右移(>>)、无符号右移(>>>)。
左移运算符的写法是”<<“,看上去向两个向左的箭头,表示要把二进制数据在内存空间中向左边移动。使用左移运算符时,把想进行位移操做的操做数放最左面,以后写上左移运算符,在左移运算符的右边写上移动的位数。例如:"5<<2"就表示对数字5进行左移2位的操做。下图展现了进行左移操做以后,二进制串在内存中是怎样变化的:
能够看到这个二进制串在内存中总体向左移动了两位,那么最左边的两位就跑到内存单元的外面去了,这两位数字将会被舍弃,右边空出的两位用0补齐。
左移运算有乘以2的N次方的效果。一个数向左移动1位,就至关于乘以2的1次方,移动两位就至关于乘以2的2次方,也就是乘以4。位移操做在实际运算时远远快于乘法操做,因此在某些对运算速度要求很是高的场合,能够考虑用左移代替乘以2的N次方的乘法操做。可是须要提醒你们注意三个细节:
首先:位移操做同取反操做同样,并不能改变变量自己的值,所能改变的仅是存储在操做数栈中那个数据的值,不理解这句话意思的小伙伴看下图:
其次:当位移的位数不少时,致使最左边的符号位发生变化,就再也不具备乘以2的N次方的效果了。好比十进制的5转换为补码形式是:前面29个0最后3位是101,若是移动29位,那么最前面的符号位就变成了1,此时运算的结果就成为了一个负数,再也不是5乘以2的29次方的乘法结果。
最后:对于byte/short/int三种类型的数据,Java语言最多支持31位的位移运算,对于long类型的数据而言,最多支持63位的位移运算。这多是由于Java语言的设计者认为位移的偏移量已经超过存储数据自己的长度,没有什么意义。小伙伴们能够试一下数字5左移32位是什么结果。
右移运算分为两种,分别是带符号右移和无符号右移。首先咱们来讲说带符号右移运算符。带符号右移运算符的写法是”>>“,与左移运算符的方向刚好相反。所谓带符号右移就是指当二进制串向右边移动之后,左边空出的位用”符号位上的数字”填充,说的更直白一点,若是是正数,二进制串右移的时候用0来填充左边的空位,而对于负数而言,右移的时候用1来填充左边的空位,以下图:
从图上能够清楚的看到带符号右移操做在二进制串移动以后左边空位是怎样被填充的。以前强调过,左移操做具备乘以2的N次方的效果,其实带符号右移也具备”相似”除以2的N次方的效果。请注意,这里说的是”相似”除以2的N次方的效果,为何要加上”相似”两个字呢?就是由于对于正数而言,带符号右移以后产生的数字确实等于除以2的N次方,可是对于负数而言,带符号右移是在除以2的N次方的结果之上还要减去1。好比对于正5,带符号右移两位的结果是1,而对于-5,带符号右移两位的结果是-2,也就是-5被2的2次方整除再减去1的结果。
带符号右移的操做能够保证移动以前和移动以后数字的正负属性不变,原来是正数,无论移动多少位,移动以后仍是正数,原来是负数,移动以后仍是负数。另外,咱们还能够继续深挖一下这个特性,从而获得一个结论:对于任何一个byte、short或者int类型的数据而言,带符号右移31位以后,获得的必然是0或者是-1。对于long类型的数据而言,带符号右移63位以后,获得的也必然是0或者是-1。可以得出这个结论的依据也很简单,就是由于对于byte、short和int类型的变量而言,若是是正数,带符号右移31位以后产生的二进制串必然所有是0,转换成对应的十进制数就是0;而对于负数而言,带符号右移31位以后产生的二进制串必然所有是1,转换成十进制数就是-1。对于long类型的数据,带符号右移63位也具备相同效果。
前文已说过:右移运算分为两种,分别是带符号右移和无符号右移。如今再来说解无符号右移。无符号右移运算符的写法是”>>>”,比带符号右移多了一个”>”。带符号右移的运算规则与无符号右移的运算规则差异就在于:无符号右移在二进制串移动以后,空位由0来补充,与符号位是0仍是1毫无关系,以下图:
以上图片展现了无符号右移的运算规则。对于正数而言,无符号右移和带符号右移没有什么区别,而对于负数而言,通过无符号右移会产生一个正数,由于最左边的符号位被0填充了。
到此为止,咱们已经讲解了所有的7个位运算符。不少小伙伴都会认为位运算没有太多的实用价值,学习位运算操做也只是为了应付面试。其实这种想法是彻底错误的。位运算在不少场合都有着深刻应用,可以解决不少实际问题。随后的几篇文章将详细讲解位运算在实际开发中的应用技巧。
如想系统学习Java编程,欢迎观看我在本站的视频课程。