1. 概述
程序中的所有数在计算机内存中都是以二进制的形式储存的。即 0、1 两种状态,位运算就是直接对整数在内存中的二进制位进行操作。由于是底层运算,如果运用得当可以降低空间需求和提高执行效率。
2. 位运算总览
符号 | 描述 | 运算规则 | 实例 |
---|---|---|---|
& | 与 | 两位都为1时,结果才为1。 | 1&1=1 1&0=0 0&0=0 |
| | 或 | 两位都为0时,结果才为0。 | 1|1=1 1|0=1 0|0=0 |
^ | 异或 | 两位相同为0,不同为1。 常称之为不进位加法。 | 1^1=0 1^0=1 0^0=0 |
~ | 非 | 0变1,1变0。 | ~1=0 ~0=1 |
<< | 左移 | 所有二进制位左移n位,高位舍弃,低位补0。 | 1<<2=4,此时n=2, 即0001<<2=0100。 |
>> | 右移 (算数右移) | 1.对于无符号数,所有二进制位右移n位,高位补0。 2.对于有符号数,所有二进制位右移n位,高位补符号位。 | 4>>2=1,此时n=2, 即0100>>2=0001。 -10>>1=-5,此时n=1, 即1111 0110>>1=1111 1011。 |
>>> | 无符号右移 (逻辑右移) | 所有二进制位右移n位,高位补0。 | 5>>>2=1,此时n=2, 即0101>>>2=0001。 |
3. 位运算优先级
由于位运算符的优先级比较低,在与别的运算符一起使用时需注意顺序,若不太清楚就加小括号保底。
优先级 | 运算符 | 结合方向 |
---|---|---|
1 | -,~,++,-- | 从右到左 |
2 | *,/,% | 从左到右 |
3 | +,- | 从左到右 |
4 | <<,>> | 从左到右 |
5 | >,<,<=,>= | 从左到右 |
6 | ==,!= | 从左到右 |
7 | & | 从左到右 |
8 | ^ | 从左到右 |
9 | | | 从左到右 |
4. 位运算技巧
4.1 按位与运算符 -- ( & )
4.1.1 获取某指定位上的数
// 获取24的低四位上的数
// 只需要另找一个数,令tmp的低4位为1,其余位为0
int target = 24;
int tmp = 15;
// target 11000
//& tmp & 01111
---------------------
// 01000
target & tmp = 8
4.1.2 判断奇偶
(targrt & 1) == 0
if((targrt % 2) == 0){
// TODO
}
// 可替换为
if((targrt & 1) == 0){
// TODO
}
4.1.3 提取数据的最右侧的1
a & (~a + 1)
a 110010000
~a 001101111
~a+1 001110000
a 110010000
& (~a+1) 001110000
-------------------------
000010000
4.2 按位或运算符 -- ( | )
4.2.1 将一个数据的指定位上置1
target | (1 << index)
// 将第三位上的数置1
int target = 24;
int index = 3;
// 11000
// | 00100
-------------------
// 11100
target | (1 << index) = 28
4.3 异或运算符 -- ( ^ )
异或运算符满足一下性质:
- 恒等律:任何值与自身异或结果为0,任何值与0异或结果为其自身。
- 交换律:
x^y^z = x^z^y
,这意味着异或操作的顺序可以任意交换,结果不会改变。- 结合律:
x^y^z = x^(y^z)
,这表明可以先计算一部分再与剩余部分进行异或操作。- 自反性:
x^x = 0
和x^0 = x
,这可以用来在某些情况下简化表达式。
4.3.1 不使用额外空间交换两个变量( 慎用 )
void swap(int[] arr, int i, int j){
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
注:切记 i 和 j 不能是同一位置,即两数据是在不同内存区域,否者结果可能会出错。
4.4 左移和右移运算符 -- ( <<,>> )
通常用于平替关于2^n的乘除法
a << 1 <==> a * 2
a >> 1 <==> a / 2
5. 位运算运用
5.1 有一种数出现了奇数次,其余数出现了偶数次,求出现了奇数次的那种数。
// arr { 1 1 1 2 2 3 3}
// 循环异或,更具性质 1^1^1^2^2^3^3
// 偶数次异或结果是0 --- --- ---
// 结合律可得 1^ 0 ^ 0 ^ 0 = 1
void printOddTimesNum(int[] arr) {
int eor = 0;
for (int i = 0; i < arr.length; i++) {
eor ^= arr[i];
}
System.out.println(eor);
}
5.2 有俩种数出现了奇数次,其余数出现了偶数次,求出现了奇数次的俩种数。
// arr { 6 10 6 6 4 4 12 12 3 3 }
// 6 10 4 3
// 00110 01010 00100 00011
//
// eor = a ^ b = 6 ^ 10 = 01100
//
// rightOne = 00100
// 通过rightOne分成两组
// one = 6 ^ 6 ^ 6 ^ 4 ^ 4 ^ 12 ^ 12 = 6
// other = eor ^ one = 10
public static void printOddTimesNum2(int[] arr) {
void printOddTimesNum(int[] arr) {
int eor = 0;
for (int i = 0; i < arr.length; i++) {
eor ^= arr[i];
}
// eor = a ^ b
// eor!=0
// 提取出最右的1
int rightOne = eor & (~eor + 1);
int one = 0;
for (int i = 0; i < arr.length; i++) {
// 通过rightOne分成两组
if ((arr[i] & rightOne) != 0) {
one ^= arr[i];
}
}
System.out.println(one + " " + (eor ^ one));
}