位运算操作是由处理器支持的底层操作,因此运行速度很快。尽管现代计算机处理器拥有了更长的指令流水线和更优的架构设计,使得加法和乘法运算几乎与位运算一样快,但是位运算消耗更少的资源。
你可能经常在JDK源码中看到位运算操作,因此对位运算的掌握是有必要的。
举个例子,比如java.lang.Long的hashCode()方法:
public static int hashCode(long value) {
return (int)(value ^ (value >>> 32));
}
这里就用到了异或运算 '^' 和逻辑右移运算 '>>>' ,需要注意到这里面有一个运算符优先级的问题。 优先级的问题看这里。
移位运算符
Java中移位运算有三种,分别是左移运算、算术右移、逻辑右移,对应的操作符是 '<<' 、 '>>' 和 '>>>'。移位运算符的右操作数需要进行模32的运算,如果是long类型,需要进行模64的运算。例如,1 << 35 就等同于 1 << 3。
左移运算 '<<'
左移运算将数字的二进制位全部向高位移动,低位补 0 。例如,数字 23 左移 1 位,即为 23 << 1 ,左移运算的二进制位如下:
0001 0111 (数字 23 )
左移 1 位 = 0010 1110 (数字 46 )
可以发现,左移 1 位相当于乘以 2 ,而左移 n 位相当于乘以 2 的 n 次方。需要注意的是,可能会导致溢出。
算术右移 '>>' 和 逻辑右移 '>>>'
右移运算与左移运算刚好相反,右移将数字的二进制位全部向低位移动,算术右移高位补符号位,逻辑右移高位补 0 。也就是说,数字 -105 算术右移 1 位,即 -105 >> 1,二进制位如下:
1001 0111 (数字 -105 )
算术右移 1 位 = 1100 1011 (数字 -53 )
同样地,我们发现算术右移 1 位相当于除以 2 ,由于最低位的值被掩盖了,-105 跟 -106 右移结果将是相同的。
如果是逻辑右移,即 -105 >>> 1,二进制位如下:
1001 0111 (数字 -105 )
逻辑右移 1 位 = 0100 1011 (数字 75 )
算术右移和逻辑右移的区别就在于,算术右移将补充符号位,逻辑右移将补充 0 。
异或运算 '^'
异或运算的规则是,相同为 0 ,不同为 1 。例如:
0101
XOR 0011
= 0110
结合已经学习的逻辑右移运算和异或运算,回头来看java.lang.Long的hashCode()方法,会发现作者实现得真巧妙。一个较好的hashCode()实现,应该尽量均匀地映射到 int 范围上。long 类型的 value >>> 32 ,会将高 32 位移到低 32 位,然后高 32 位补 0 。如果这个 long 类型的 value 值没有超过 int 类型的表示范围,value >>> 32 的二进制结果将是 64 个 0。即 value ^ ( value >>> 32) 将转换成 value ^ 0,而与 value ^ 0 等于 value。结果就是,当 long 类型的值不超过 int 类型的表示范围时,Long 的hashCode()方法,将直接返回对应的 int 值。也就是说,被完全均匀地 hash 映射。
或运算 '|'
或运算的规则是,有 1 为 1 ,全为 0 时为 0 。例如:
0101 (数字 5 )
OR 0011 (数字 3 )
= 0111 (数字 7 )
或运算可以用来set(1) ,举个例子,二进制数 0010 (数字 2 )通过或运算第四位可以单独被置为 1 :
0010 (数字 2)
OR 1000 (数字 8)
= 1010 (数字 10)
与运算 '&'
与运算的规则是,都为 1 时为 1,有一个为 0 就为 0 。例如:
0101 (数字 5)
AND 0011 (数字 3)
= 0001 (数字 1)
与运算可以用于 clear(0) 操作,或者对特殊的位 set(1),这里的 set(1) 与或运算是不同的,与运算 set(1) 只会让被设置的位为 1 ,例如:
0011 (数字 3)
AND 0010 (数字 2)
= 0010 (数字 2)
因为结果是 0010 ,我们便知道原数字的第 2 位是 1 。这个操作被称为位屏蔽。
再举个例子, 假设 0110 (数字 6 )是一个 4 位标记,第 1 位和第 4 位已经被 clear(0),第 2 位和第 3 位被 set(1)。现在可以通过与运算将第 3 位置为 0 :
0110 (数字 6)
AND 1011 (数字 11)
= 0010 (数字 2)
因为这个特性,与运算可以通过校验最低位的值来进行奇偶校验。例如:
0110 (数字 6)
AND 0001 (数字 1)
= 0000 (数字 0)
因为 6 AND 1 等于 0,也就是说 6 能被 2 整除,所以是偶数。
取反运算 '~'
取反运算的规则是,将二进制的每一位取反,1 变为 0, 0 变为 1。例如:
NOT 0111 (数字 7)
= 1000 (数字 8)
NOT 10101011 (数字 171)
= 01010100 (数字 84)