很多人认为位运算在实际开发过程中并没什么用,学习位运算也只是为了应付面试。这种想法是错误的,接下来我们就通过几篇连载文章介绍一下位运算在实际开发过程中的几个经典应用实例。如果对位运算规则掌握还不是很熟练,可以先阅读《Java千问16:Java语言位运算符详解》。这篇文章不仅详细讲解了Java位运算的基本规则和一些常用的运算定律,同时还在文中提到了一些常用的位运算实际应用,比如可以用位运算操作的方式快速把某个变量所在的内存单元清零,或者位运算的方式实现某个变量快速倍增等等。但文中所这提到的这几个实际应用比较简单,本次连载文章将为大家讲述的是更加复杂和实际的应用经典案例。此外,为获得更好的阅读效果,请各位读者在读本文之前先熟练掌握”补码”的计算规则。
一、判断整数的奇偶性
按照传统的思路,判断一个整数的奇偶性是通过用这个数与2求模,看运算结果是否为0。学了位运算符以后,我们可以换一种思路来考虑问题。我们知道:Java语言中,所有数字存储在内存中,都要先转换成补码的形式。任何一个偶数用补码表示出来后,它的最后一个二进制位都是0,而奇数补码的最后一个二进制位都是1。所以,我们可以通过判断这个整数的补码的最后一位二进制数是0还是1,来判断这个数是偶数还是奇数。判断的方法就是用这个数与1进行按位与的操作,如果结果为0,那么这个数就是偶数,否则就是奇数。如果大家不理解这个算法的原理,请看下图:
为了方便表述,我们把要判断奇偶性的数字称为a。图中,以横线为界,分别展示了a为偶数和奇数的情况下,与数字1进行按位与操作的结果。其中,上面的二进制串是a的补码,下面二进制串是数字1的补码。可以看到,数字1被转换成补码之后,总共有32位,其中前31位都是0,最后1位的值是1。这就导致a补码的前31位无论是0还是1,与数字1的补码进行按位与运算,运算结果的前31位都只能是0,决定最终运算结果的就只有a补码最右边的那个二进制位。如果最右边那一位是0,那么a就是偶数,a与1按位与运算的最终结果是数字0。反过来,如果最右边那一位是1,那么a就是奇数,a与1按位与运算的最终结果是数字1。因此我们只要看一下a与数字1进行按位与运算的结果就知道a的奇偶性了。具体的程序实现如下:
二、求绝对值
常规算法求绝对值的思路是:首先判断一个数a是否>=0,如果a>=0,则返回a本身,否则返回a的相反数。这个过程包含判断和选择两个步骤。如果使用位运算符来实现求绝对值,可以省略掉判断的步骤,直接返回运算结果。下面来讲解一下使用位运算符求绝对值的基本原理。我们知道:任何一个二进制位上的数,与0进行异或运算,运算的结果都与这个二进制位上的数相同。把这个结论扩展一下,从原来某个数的单独的一个二进制位扩展到这个数字本身,可以得出另外两个结论,第一个结论: 任何一个整数与0进行异或运算后依然保持不变。 如果小伙伴不理解的话,请看下图,我们以数字5作为例子分析讲解:
另一个结论: 任何一个整数与-1进行异或运算后再加上1,得到的结果就是这个数的相反数 。如果不理解的话,还是看下图,仍然是以数字5分析讲解:
以上两个结论中,第一个结论比较容易理解。我们简单的解释一下第二个结论的原理。第二个结论能够成立的关键就在于,数字-1用补码的形式表示出来,恰好是一个32位全为1的二进制串。这个二进制串与任何一个其他二进制串进行按位异或运算,都可以达到”取反”的效果,而按照补码的计算规则,一个正数按位取反后再加1,得到的就是它相反数。比如图中的数字5,按位取反之后,再加1得到的就是-5。
至此,我们已经知道怎样通过位运算的方式获得一个数的相反数了。在《Java千问16:Java语言位运算符详解》一文中还讲过:int类型的正数经过带符号右移31位之后,得到的必然是0,而负数经过带符号右移31位得到的是-1。我们就可以通过右移所得到的这个0或者-1,判断出这个数是正数还是负数。知道数字的正负属性,然后再用位运算的方式得到这个数本身或者是它的相反数,就能求出这个数的绝对值。按照这个思路,我们就可以来编写求绝对值的程序了,程序如下:
示例程序中的变量a是int型,如果改为long型,对a带符号右移63位也可有相同的运算效果。
(未完待续...)