一、什么是位运算
程序中的数在计算机中都是以二进制形式存储,位运算就是直接对整数在内存中的二进制位进行操作,位运算 包括位逻辑运算和移位运算,位逻辑运算能够方便设置或屏蔽某个字节的一位或几位,也可以对两个数按位相加,移位运算可以对内存中某个二进制左移或右移几位
二、Java中提供的七种位运算
2.1、位运算核心操作
位运算符 | 名称 | 含义 | 举例 |
& | 按位与 | 将参与运算的两个二进制数进行&与运算,如果两个二进制位都是1,则与运算的结果为1,其他全都为0。(0与任意数N&运算都是0) | a&b |
| | 按位或 | 将参与运算的两个二进制数进行|或运算,两个二进制位只要其中1个是1 ,那么就是1,如果2个二进制位都是0则表示0。(0与任意数N|运算都是任意数N) | a|b |
^ | 按位异或 | 将参与运算的两个二进制数进行^异或运算,如果2个二进制位都是0或者都是1,那么就是0,如果两个二进制位不同,则为1。任何数与0异或运算都是其本身,任何数与自己进行异或运算结果都是0 | a^b |
~ | 按位非 | 一元操作符,按位取反。每个二进制位上都取相反值,1变成0,0变成1。 | ~a |
<< | 左移 | 将一个数各二进制位全部向左移动若干位,左移运算没有有符号和无符号左移动,在左移时,移除高位的同时在低位补0 | a<<2 |
>> | 右移 | 又称为有符号右移。将一个数各二进制位全部向右移动若干位,若参与运算的数字为正数,则在高位补0;若为负数,则在高位补1。 | a>>2 |
>>> | 无符号右移 | 将一个数各二进制位全部向右移动若干位,右移之后左边都是补上0。忽略符号位 | a>>>2 |
位运算优先级从高到低依次为:取反(~)、左移位(<<)、右移位(>>)、无符号右一(>>>)、位与(&)、位异或(^)、位或(|)
2.2、位运算扩展操作
&= 按位与赋值
|= 按位或赋值
^=按位非赋值
<<= 赋值左移
>>=右移赋值
>>>=无符号右移赋值
四、位运算的应用
1、判断奇偶数
if(n&1==1){ //n是个奇数 } |
其核心就是判断二进制最后一位是否为1,若是,则结果加上2^0=1,一定是奇数
2、交换两个数
a = a ^ b; b = a^b //b = (a^b)^b=a^0 = a a = a^b // a = (a^b)^a = b |
3、变化符号
通过对数值X取反并加1来改变其符号
x = ~x + 1; |
4、清除最低位的1
使用 x & (x - 1) 可以将最低位的 1 清零,这个技巧可以用于计算二进制中 1 的数量或者确定一个数是否为2的幂
5、得到最低位的1
使用 x & (-x) 可以提取出 x 的最低位的 1。
6、设置和清除特定位
设置(Set)位:要设置 x 的第 n 位为 1,可以用 x |= (1 << n)。
清除(Clear)位:要清除 x 的第 n 位,可以用 x &= ~(1 << n)。
7、位运算在ByteBuffer源码中的应用
static int getIntB(ByteBuffer bb, int bi) {
return makeInt(bb._get(bi ),
bb._get(bi + 1),
bb._get(bi + 2),
bb._get(bi + 3));
}
// -- get/put int --
static private int makeInt(byte b3, byte b2, byte b1, byte b0) {
return (((b3) << 24) |
((b2 & 0xff) << 16) |
((b1 & 0xff) << 8) |
((b0 & 0xff) ));
}
//(b3) << 24)将 b3 左移 24 位。由于 int 类型是 32 位的,因此将 b3 左移 24 位可以将 b3 放到结果的最高位(最高有效字节位置)。
// b2 & 0xff:这一步是为了确保 b2 只取其最低的 8 位。因为 byte 类型是有符号的,如果 b2 的最高位是 1,则整数会被视为负数,这不是我们想要的。通过 & 0xff 操作,可以将 b2 转换为无符号整数,并且只保留其最低的 8 位。
// << 16:将 b2 左移 16 位,将其放置到结果的第二个字节的位置。
// b1 & 0xff:同样的操作,确保 b1 只取其最低的 8 位。
// << 8:将 b1 左移 8 位,将其放置到结果的第三个字节的位置。
// b0 & 0xff:同样的操作,确保 b0 只取其最低的 8 位。
// 最后一步不需要左移,因为 b0 将放置在结果的最低字节的位置。
这段代码实现了将四个字节按照大端字节序(Big Endian)组合成一个整数的功能。在这个函数中,b3
表示最高有效字节(Most Significant Byte,MSB),b0
表示最低有效字节(Least Significant Byte,LSB)
通过按照大端字节序的顺序组合字节,确保了生成的整数的高位包含了原始字节序列中的高位字节,低位包含了原始字节序列中的低位字节。
8、位运算实现加减乘除
1)加法
加法可以通过迭代使用异或(^)来计算无进位和,与(&)然后左移来计算进位,然后将它们加在一起,直到没有进位为止。
public class BitwiseOperations { public int add(int a, int b) { while (b != 0) { int sum = a ^ b; // 无进位和 int carry = (a & b) << 1; // 进位 a = sum; b = carry; } return a; } } |
2)减法
减法可以通过取补码来实现。要从a中减去b,我们可以添加b的补码。补码可以通过取反(~)再加1得到
add函数在二进制加法中已经实现
public int subtract(int a, int b) { // 加上b的补码(即 -b) return add(a, add(~b, 1)); } |
3)乘法
乘法可以通过位移和加法来实现。我们对乘数进行迭代,如果当前位为1,则将被乘数加到结果上,然后被乘数左移一位。
public int multiply(int a, int b) { int result = 0; while (b != 0) { if ((b & 1) != 0) { result = add(result, a); } a <<= 1; b >>>= 1; // 使用逻辑右移,对于负数也适用 } return result; } |
4)除法
除法是乘法的逆运算。我们可以找到商,通过不断从被除数中减去除数的倍数来实现。
public int divide(int dividend, int divisor) { if (divisor == 0) { throw new ArithmeticException("Cannot divide by zero"); } if (dividend == Integer.MIN_VALUE && divisor == -1) { // 特殊情况处理,避免溢出 return Integer.MAX_VALUE; }
int result = 0; int sign = (dividend < 0) ^ (divisor < 0) ? -1 : 1; // 判断结果的符号
// 将被除数和除数都转换为正数 dividend = Math.abs(dividend); divisor = Math.abs(divisor);
while (dividend >= divisor) { int tempDivisor = divisor; int quotient = 1; while ((tempDivisor << 1) <= dividend) { tempDivisor <<= 1; quotient <<= 1; }
dividend -= tempDivisor; result = add(result, quotient); }
return sign == -1 ? subtract(0, result) : result; } |
在所有这些函数中,我们必须处理溢出的情况,尤其是当涉及到最小整数值的时候。除法中有一个特殊情况,当最小值除以-1时,会产生溢出,因为其绝对值比Integer.MAX_VALUE大1,所以需要特殊处理。