Java位运算知识点详解
在日常的Java开发中,位运算使用的不多,使用的更多的是算数运算(+、-、*、/、%)、关系运算(<、>、<=、>=、==、!=)和逻辑运算(&&、||、!),所以相对来说对位运算不是那么熟悉,本文将以Java的位运算来详细介绍下位运算及其应用。
1、位运算起源
位运算起源于C语言的低级操作,Java的设计初衷是嵌入到电视机顶盒内,所以这种低级操作方式被保留下来。所谓的低级操作,是因为位运算的操作对象是二进制位,但是这种低级操作对计算机而言是非常简单直接,友好高效的。在简单的低成本处理器上,通常位运算比除法快得多,比乘法快几倍,有时比加法快得多。虽然由于较长的指令流水线和其他架构设计选择,现代处理器通常执行加法和乘法的速度与位运算一样快,但由于资源使用减少,位运算通常会使用较少的功率,所以在一些Java底层算法中,巧妙的使用位运算可以大量减少运行开销。
2、位运算详解
Java位运算细化划分可以分为按位运算和移位运算,见下表。
细化 符号 描述 运算规则 按位运算 & 与 两位都为1,那么结果为1 | 或 有一位为1,那么结果为1 ~ 非 ~0=1,~1=0 ^ 异或 两位不相同,结果为1 移位运算 << 左移 各二进制位全部左移N位,高位丢弃,低位补0 >> 右移 各二进制位全部右移N位,若值为正,则在高位插入0,若值为负,则在高位插入1 >>> 无符号右移 各二进制位全部右移N位,无论正负,都在高位插入0
在进行位运算详解之前,先来普及下计算机中数字的表示方法。对于计算机而言,万物皆0、1,所有的数字最终都会转换成0、1的表示,有3种体现形式,分别是:原码、反码和补码。
原码:原码表示法在数字前面增加了一位符号位,即最高位为符号位,正数位该位为0,负数位该位为1.比如十进制的5如果用8个二进制位来表示就是00000101,-5就是10000101。
反码:正数的反码是其本身,负数的反码在其原码的基础上,符号位不变,其余各个位取反。5的反码就是00000101,而-5的则为11111010。
补码:正数的补码是其本身,负数的补码在其原码的基础上,符号位不变,其余各位取反,最后+1。即在反码的基础上+1。5的反码就是00000101,而-5的则为11111011。
了解了这几个概念后,我们现在先记住一个结论,那就是在计算机系统中,数字一律用补码来表示、运算和存储,具体的原因可以看这篇文章的讨论,这里不做更多讨论,因为不是本文的重点。
2.1与运算(&)
规则:转为二进制后,两位为1,则结果为1,否则结果为0。
举例:
十进制 二进制(正数原码、反码、补码一致) 10 00000000000000000000000000001010 &12 &00000000000000000000000000001100 = = 8 00000000000000000000000000001000
十进制 二进制(原码) -6 10000000000000000000000000000110 &-2 &10000000000000000000000000000010 十进制 二进制(反码) -6 11111111111111111111111111111001 &-2 &11111111111111111111111111111101 十进制 二进制(补码) -6 11111111111111111111111111111010 &-2 &11111111111111111111111111111110 = = -6 11111111111111111111111111111010
最后的计算结果11111111111111111111111111111010还是补码的形式,要看其十进制,还需要先转成二进制原码。
先转反码:11111111111111111111111111111010-1=11111111111111111111111111111001,得反码11111111111111111111111111111001。
再转原码:在反码的基础上转原码,符号位不变,其他各位取反,得10000000000000000000000000000110。第一位1代表负数,后面0110转成十进制是6,得-6。
2.2或运算(|)
规则:转为二进制后,有一位为1,则结果为1,否则结果为0。
举例:
十进制 二进制(正数原码、反码、补码一致) 10 00000000000000000000000000001010 |12 |00000000000000000000000000001100 = = 14 00000000000000000000000000001110
十进制 二进制(原码) -6 10000000000000000000000000000110 |-2 |10000000000000000000000000000010 十进制 二进制(反码) -6 11111111111111111111111111111001 |-2 |11111111111111111111111111111101 十进制 二进制(补码) -6 11111111111111111111111111111010 |-2 |11111111111111111111111111111110 = = -2 11111111111111111111111111111110
2.3非运算(~)
规则:转为二进制后,~0=1,~1=0。
举例:
十进制 二进制(正数原码、反码、补码一致) ~7 ~00000000000000000000000000000111 = = -8 11111111111111111111111111111000(补码需转换为原码)
11111111111111111111111111111000-1得反码,可以把1000看成是0112,得反码
11111111111111111111111111110111。根据反码得原码10000000000000000000000000001000。
十进制 二进制(原码) ~(-6) ~10000000000000000000000000000110 十进制 二进制(反码) ~(-6) ~11111111111111111111111111111001 十进制 二进制(补码) ~(-6) ~11111111111111111111111111111010 = = 5 00000000000000000000000000000101(正数原码、反码、补码一致)
2.4异或运算(^)
规则:转为二进制后,两位不相同,结果为1,否则为0。
举例:
十进制 二进制(正数原码、反码、补码一致) 15^2 00000000000000000000000000001111 ^00000000000000000000000000000010 = = 13 00000000000000000000000000001101
2.5左移运算(<<)
规则:转为二进制后,各二进制位全部左移N位,高位丢弃,低位补0。
举例:
十进制 二进制(正数原码、反码、补码一致) 2<<2 00000000000000000000000000000010 = 0000000000000000000000000000001000 8 00000000000000000000000000001000
十进制 二进制(先取补码再对补码操作位移) -2<<2 10000000000000000000000000000010(原码) 11111111111111111111111111111101(反码) 11111111111111111111111111111110(补码) 1111111111111111111111111111111000 11111111111111111111111111111000(补码) 11111111111111111111111111110111(反码) -8 10000000000000000000000000001000(原码)
2.6右移运算(>>)
规则:转为二进制后,各二进制位全部右移N位,若值为正,则在高位插入0,若值为负,则在高位插入1。
举例:
十进制 二进制(正数原码、反码、补码一致) 2>>2 00000000000000000000000000000010 = 0000000000000000000000000000000010 0 00000000000000000000000000000000
十进制 二进制(先取补码再对补码操作位移) -6>>2 10000000000000000000000000000110(原码) 11111111111111111111111111111001(反码) 11111111111111111111111111111010(补码) 1111111111111111111111111111111010 11111111111111111111111111111110(补码) 11111111111111111111111111111101(反码) -2 10000000000000000000000000000010(原码)
2.7无符号右移运算(>>>)
规则:转为二进制后,各二进制位全部右移N位,无论正负,都在高位插入0。
举例:
十进制 二进制(先取补码再对补码操作位移) -1>>>1 10000000000000000000000000000001(原码) 11111111111111111111111111111110(反码) 11111111111111111111111111111111(补码) 011111111111111111111111111111111 01111111111111111111111111111111(补码) 01111111111111111111111111111110(反码) 溢出,只能表示到int的最大值2147483647 10000000000000000000000000000001(原码)
3、应用
3.1不用额外的变量实现两个数字互换
见参考资料中的BitOperationTest,方法reverse通过三次异或操作完成了两个变量值的替换。
证明很简单,我们只需要明白异或运算满足下面规律(实际不止如下规律):
0^a=a,a^a=0;
a^b=b^a;
a^b^c=a^(b^c)=(a^b)^c;
a^b^a=b;
假设a,b两个变量,经过如下步骤完成值交换:a=a^b,b=b^a,a=a^b。
证明如下:
因为a^b=b^a,又a=a^b,b=b^a。故b=b^a=b^(a^b)=a。
继续a=a^b,a=(a^b)^b^(a^b),故a=b。完成值交换。
3.2不用判断语句实现求绝对值
公式如下:(a^(a>>31))-(a>>31)
先整理一下使用位运算取绝对值的思路:若a为正数,则不变,需要用异或0保持的特点;若a为负数,则其补码为原码翻转每一位后+1,先求其原码,补码-1后再翻转每一位,此时需要使用异或1具有翻转的特点。
任何正数右移31后只剩符号位0,最终结果为0,任何负数右移31后也只剩符号位1,溢出的31位截断,空出的31位补符号位1,最终结果为-1.右移31操作可以取得任何整数的符号位。
那么综合上面的步骤,可得到公式。a>>31取得a的符号,若a为正数,a>>31等于0,a^0=a,不变;若a为负数,a>>31等于-1,a^-1翻转每一位。
3.3判断一个数的奇偶性
通过与运算判断奇偶数,伪代码如下:
n&1==1?”奇数”:”偶数”
奇数最低位肯定是1,而1的二进制最低位也是1,其他位都是0,所以所有奇数和1与运算结果肯定是1。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。