Java 中的位运算
Java 中的位运算(Bitwise Operations) 是直接对整数在内存中的二进制位进行操作的一类高效运算。它们常用于性能优化、状态压缩(如标志位)、算法题优化、加密和底层系统编程等领域。
一、Java 支持的位运算符
| 运算符 | 名称 | 说明 |
|---|---|---|
& | 按位与 | 两个位都为 1,结果才为 1 |
| | 按位或 | 两个位中有一个为 1,结果为 1 |
^ | 按位异或 | 两个位不同,结果为 1 |
~ | 按位取反 | 0 变 1,1 变 0(包括符号位) |
<< | 左移 | 所有位向左移动,低位补 0 |
>> | 右移(带符号) | 高位补符号位(正数补 0,负数补 1) |
>>> | 无符号右移 | 高位始终补 0,忽略符号 |
注意:Java 中整数使用补码表示,且位运算操作的是 32 位(
int)或 64 位(long) 的二进制形式。
二、运算符详解
1. 按位与 &
用途:掩码(masking)、判断某一位是否为 1
规则:只有对应位都为 1,结果才为 1,否则为 0。
| 位1 | 位2 | 结果 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
示例:
javaint a = 5; // 二进制: 0000 0101 int b = 3; // 二进制: 0000 0011 System.out.println(a & b); // 输出 1 (0000 0001)
2. 按位或 |
- 用途:设置某一位为 1,合并两个数的位
- 规则:对应位只要有一个为 1,结果就为 1
| 位1 | 位2 | 结果 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
示例:
javaint a = 5; // 0101 int b = 3; // 0011 System.out.println(a | b); // 输出 7 (0111)
3. 按位异或 ^
- 用途:比较两个数的对应位差异、加密解密、交换变量
- 规则:对应位不同为 1,相同为 0。
| 位1 | 位2 | 结果 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
特性:
a ^ a = 0:任何数与自身异或,结果为 0(所有位相同)a ^ 0 = a:任何数与 0 异或,结果不变(0 的所有位为 0)a ^ b ^ a = b:异或运算满足交换律和结合律,可用于恢复原值,可用于无临时变量交换两个数
示例:
javaint a = 5; // 0101 int b = 3; // 0011 System.out.println(a ^ b); // 输出 6 (0110)交换两个数的示例:
javaint x = 10; // 1010 int y = 20; // 10100 System.out.println("交换前: x=" + x + ", y=" + y); // 交换前: x=10, y=20 // 无临时变量交换 x = x ^ y; // x 变为 x ^ y y = x ^ y; // y 变为 (x ^ y) ^ y = x x = x ^ y; // x 变为 (x ^ y) ^ x = y System.out.println("交换后: x=" + x + ", y=" + y); // 交换后: x=20, y=10⚠️ 注意:这种交换方式不适用于两个变量指向同一内存地址(如
a = b),会导致结果为 0。实际开发中应该使用临时变量,可读性更高。java// 危险示例: int a = 10; int b = a; // b 和 a 值相同(或引用同一变量) a ^= b; // a = 0 b ^= a; // b = 0 a ^= b; // a = 0 → 两个变量都变成 0!规则:0 变 1,1 变 0
示例:
javaint a = 5; // 0000 0101 System.out.println(~a); // 输出 -6(二进制:1111 1010,补码表示)
5. 左移 <<
相当于 乘以 2 的 n 次方(可能溢出)
示例:
javaint a = 5; // 二进制: 0000 0101 System.out.println(a << 1); // 10 (5 × 2) (二进制:0000 1010) System.out.println(a << 2); // 20 (5 × 2 × 2) (二进制:0001 0100)💡 注意:Java 中没有无符号左移(如
<<<),因为左移操作总是低位补 0,与符号无关,因此<<对有符号数和无符号数的行为完全一致。高位补符号位,保持数值符号
相当于 除以 2 的 n 次方(向下取整)
示例:
javaint a = -8; // 补码: 1111 1000 System.out.println(a >> 1); // -4 ('1'111 1100) 注意符号位补1
7. 无符号右移 >>>(逻辑右移)
高位一律补 0,结果恒为非负
示例:
javaint a = -1; // 32 位全为 1 System.out.println(a >>> 1); // 2147483647 (0111...1111)
三、常见应用场景
1. 状态压缩(Flags)
用一个整数的每一位表示一个布尔状态:
final int READ = 1 << 0; // 第0位
final int WRITE = 1 << 1; // 第1位
final int EXEC = 1 << 2; // 第2位
int perms = READ | WRITE; // 0011
// 检查权限
if ((perms & READ) != 0) {
System.out.println("可读");
}
// 添加权限
perms |= EXEC; // 01112. 快速乘除 2 的幂
x << 3 // 等价于 x * 8
x >> 2 // 等价于 x / 4(向下取整)3. 判断奇偶性
if ((x & 1) == 1) {
// x 是奇数
}4. 交换两个整数(技巧,慎用)
a ^= b; // a = a^b
b ^= a; // b = 原 a
a ^= b; // a = 原 b5. 算法优化(如 LeetCode - 36. 有效的数独)
用 int 的低 9 位记录数字 1–9 是否出现:
int row = 0;
int num = 5;
// 标记数字 5 已出现
row |= (1 << num);
// 检查数字 5 是否已存在
if ((row & (1 << num)) != 0) {
// 存在!
}- 解释:
row的低9 位记录数字 1–9 是否出现,初始化为全0row |= (1 << num)拆分为 2 个步骤:1 << num:将1左移num位,生成一个只有第num位为1的数二进制形式为 ... 0001 0000row |= res:row = row | (1 << num)将row与生成的数进行按位或运算,将第num位置为1
(row & (1 << num)) != 0按位与运算检查该数字是否已出现
四、注意事项
运算符优先级低:务必使用括号
✅ 正确:
(x & mask) != 0❌ 错误:
x & mask == 0(==优先级更高)运算符 优先级(从高到低) ==,!=高 &中 ^中 |中 &&,||低 这能直观解释为什么
(x & mask) == 0必须加括号。仅适用于整数类型:
byte、short、int、long浮点数(float/double)不能进行位运算。负数使用补码表示:理解
~、>>、>>>在负数下的行为差异。
五、速查表
| 操作 | 表达式 |
|---|---|
判断第 n 位是否为 1 | (x & (1 << n)) != 0 |
将第 n 位设为 1 | x | = (1 << n) |
将第 n 位清零 | x &= ~(1 << n) |
翻转第 n 位 | x ^= (1 << n) |
| 乘以 2ⁿ | x << n |
| 除以 2ⁿ(向下取整) | x >> n |
掌握位运算,在某些场景下,能提升代码效率,写出更优雅的解决方案。