按位运算符直接操作二进制位,是编程中处理位级操作、性能优化、状态标记的核心工具。以下是Java / 通用编程语言中按位运算符的完整规律,包含核心定义、常用技巧和边界场景。
一、核心按位运算符(7 个)
| 运算符 | 名称 | 符号 | 运算规则(以 a=6 (0110)、b=3 (0011) 为例) | 结果(二进制) | 结果(十进制) |
|---|---|---|---|---|---|
| & | 按位与 | AND | 对应位都为 1 则为 1,否则为 0 | 0010 | 2 |
| | | 按位或 | OR | 对应位有 1 则为 1,否则为 0 | 0111 | 7 |
| ^ | 按位异或 | XOR | 对应位不同则为 1,相同则为 0 | 0101 | 5 |
| ~ | 按位取反 | NOT | 每一位取反(1→0,0→1,含符号位) | 11111111 11111001(补码) | -7(Java int) |
| << | 左移 | SHL | 左移 n 位,低位补 0,高位溢出舍弃 | 6<<1=1100 | 12 |
| >> | 右移(算术) | SHR | 右移 n 位,高位补符号位(正数补 0,负数补 1) | 6>>1=0011 | 3 |
| >>> | 右移(逻辑) | USHR | 右移 n 位,高位补 0(仅无符号右移,Java 特有) | -6>>>1=2147483645 | 2147483645 |
基础规则补充
- 按位与(&):
- 任何数 & 0 = 0;任何数 & 自身 = 自身;
- 常用:判断奇偶(
num & 1 == 0为偶数)、提取指定位(num & (1<<k)判断第 k 位是否为 1)。
- 按位或(|):
- 任何数 | 0 = 自身;任何数 | 全 1 = 全 1;
- 常用:置 1 指定位(
num | (1<<k)将第 k 位设为 1)。
- 按位异或(^):
- 任何数 ^ 0 = 自身;任何数 ^ 自身 = 0;满足交换律 / 结合律(
a^b^a = b); - 常用:交换两个数(
a=a^b; b=a^b; a=a^b)、找唯一出现奇数次的数。
- 任何数 ^ 0 = 自身;任何数 ^ 自身 = 0;满足交换律 / 结合律(
- 按位取反(~):
- 补码规则:
~num = -num - 1(如~6 = -7,~-3 = 2); - 注意:Java 中 int 是 32 位,取反会包含符号位,无单独 “无符号取反”。
- 补码规则:
- 移位运算:
- 左移(<<):等价于乘以 2n(无溢出时),如
num << 3 = num * 8; - 算术右移(>>):等价于除以 2n 向下取整(如
7>>1=3,-7>>1=-4); - 逻辑右移(>>>):仅对正数和 >> 一致,负数右移后变为正数(因为高位补 0)。
- 左移(<<):等价于乘以 2n(无溢出时),如
二、按位运算核心规律
1. 位运算与数值运算的等价性(无溢出)
| 位运算表达式 | 等价数值运算 | 适用场景 |
|---|---|---|
num << n | num * (2^n) | 快速乘法(性能优于 *) |
num >> n | num / (2^n)(向下取整) | 快速除法 |
num & (num-1) | 清除 num 最右侧的 1 | 统计 1 的个数、判断 2 的幂 |
num & (-num) | 提取 num 最右侧的 1 | 树状数组(BIT)核心 |
a ^ b ^ b | a | 还原原值 |
2. 常用位运算技巧(高频)
(1)判断是否为 2 的幂
原理:2 的幂的二进制只有 1 个 1,num & (num-1) == 0(需排除 num=0)。
java
运行
boolean isPowerOf2(int num) { return num > 0 && (num & (num - 1)) == 0; }(2)统计二进制中 1 的个数
方法 1:逐位判断(通用)
java
运行
int countOne(int num) { int count = 0; while (num != 0) { count += num & 1; // 取最低位 num = num >>> 1; // 无符号右移(避免负数死循环) } return count; }方法 2:快速消去最右侧 1(效率更高)
java
运行
int countOne(int num) { int count = 0; while (num != 0) { num &= num - 1; // 消去最右侧的1 count++; } return count; }(3)交换两个数(无需临时变量)
原理:异或的自反性(a^b^b = a)。
java
运行
void swap(int[] arr, int i, int j) { if (i == j) return; arr[i] = arr[i] ^ arr[j]; arr[j] = arr[i] ^ arr[j]; // 等价于原arr[i] arr[i] = arr[i] ^ arr[j]; // 等价于原arr[j] }(4)将第 k 位设为 0/1
- 设为 0:
num & ~(1 << k)(先构造第 k 位为 0、其余为 1 的掩码,再与运算); - 设为 1:
num | (1 << k)(构造第 k 位为 1 的掩码,或运算);
java
运行
// 第k位设为0(k从0开始,最低位为0) int setBit0(int num, int k) { return num & ~(1 << k); } // 第k位设为1 int setBit1(int num, int k) { return num | (1 << k); }(5)两个数的加减(不用 +/-)
原理:异或算无进位和,与运算算进位,循环直到进位为 0。
java
运行
// 加法:a + b int add(int a, int b) { while (b != 0) { int carry = (a & b) << 1; // 进位(只有1&1才产生进位) a = a ^ b; // 无进位和 b = carry; // 进位赋值给b,循环直到无进位 } return a; } // 减法:a - b = a + (-b),-b = ~b + 1 int sub(int a, int b) { return add(a, add(~b, 1)); }三、边界与易错点
- 溢出问题:
- 左移可能溢出(如
Integer.MAX_VALUE << 1变为负数),移位运算不做溢出检查; - 示例:
1 << 31(int 范围是 - 2^31 ~ 2^31-1),结果为-2147483648(溢出为负数)。
- 左移可能溢出(如
- 负数的位运算:
- 所有位运算基于补码,负数的最高位是 1,算术右移会补 1(如
-1 >> 100 = -1); - 逻辑右移(>>>)对负数会将符号位变为 0,如
-1 >>> 1 = 2147483647(int 最大值)。
- 所有位运算基于补码,负数的最高位是 1,算术右移会补 1(如
- 优先级问题:
- 位运算优先级低于算术运算,高于赋值运算;
- 错误示例:
num & 1 == 0等价于num & (1 == 0)(错误),需加括号:(num & 1) == 0。
- 0 和 1 的特殊处理:
~0 = -1(所有位为 1),~1 = -2;0 ^ num = num,1 & num = num & 1(取最低位)。
四、应用场景总结
| 场景 | 核心位运算 | |
|---|---|---|
| 状态标记(如权限) | 按位或(置 1)、按位与(判断) | |
| 数值快速运算(乘除 2^n) | 移位运算 | |
| 数组去重 / 找唯一数 | 按位异或 | |
| 二进制位操作(统计 1、置位) | &、 | 、^、~ |
| 算法优化(如快速排序、树状数组) | &(lowbit) | |
| 加密 / 哈希算法 | 异或、移位组合 |
按位运算的核心优势是性能极高(直接操作硬件层二进制),在底层编程、算法竞赛、高性能框架中广泛使用,掌握上述规律可大幅简化位级逻辑的实现。