最近刷到一个有趣视频指出 a = a ^ b ^ b,因此来研究研究位运算,可谓是“暗藏玄机”,十分 Interesting 且实用。
1. 异或 (XOR ^)
1.1 基本定义
异或(XOR,exclusive OR)是一种逻辑运算,符号通常为 ^。它的运算规则是:相同为 0,相异为 1。
数学表达式:
0 ^ 0 = 00 ^ 1 = 11 ^ 0 = 11 ^ 1 = 0
1.2 数学性质
异或运算具有以下重要数学性质:
- 交换律:
a ^ b = b ^ a - 结合律:
(a ^ b) ^ c = a ^ (b ^ c) - 自反性:
a ^ a = 0 - 恒等性:
a ^ 0 = a - 可逆性:如果
a ^ b = c,则a ^ c = b且b ^ c = a
1.3 在编程中的应用
1.3.1 简单加解密与混淆
用于简单的对称加解密,以及订单系统中自增号部分的混淆。
原理:明文 ^ 密钥 = 密文;密文 ^ 密钥 = 明文。
推导:
利用结合律和自反性:a ^ b ^ b = a ^ (b ^ b) = a ^ 0 = a。
代码示例 (Python):
def xor(data, key):
result = []
for i in range(len(data)):
k = key[i % len(key)]
result.append(data[i] ^ k)
return bytes(result)
1.3.2 不使用临时变量交换两个整数
推导:
a = a ^ bb = a ^ b→ 代入得(a ^ b) ^ b = a(此时 b 变成了原 a)a = a ^ b→ 代入得(a ^ b) ^ a = b(此时 a 变成了原 b)
代码示例 (Python):
def swap_without_temp(a, b):
a ^= b
b ^= a
a ^= b
return a, b
print(swap_without_temp(5, 10)) # 输出: (10, 5)
1.3.3 只出现一次的数字(LeetCode 136)
用于查找只出现一次的数字、成对缺失查找、数据去重等。
原理:利用 a ^ a = 0 和 a ^ 0 = a。
代码示例 (Python):
def find_single_number(nums):
result = 0
for num in nums:
result ^= num
return result
# 示例
print(find_single_number([4, 1, 2, 1, 2])) # 输出: 4
2. 或 (OR |)
2.1 基本定义
或(OR,inclusive OR)是一种基本的逻辑运算,符号通常为 |。
运算规则:只要有一个操作数为 1,结果即为 1;仅当所有操作数为 0 时,结果为 0。
真值表:
| A | B | A | B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
2.2 数学性质
- 交换律:
a | b = b | a - 结合律:
(a | b) | c = a | (b | c) - 幂等性:
a | a = a - 恒等性:
a | 0 = a - 吸收性:
a | 1 = 1 - 分配律(对与运算):
a | (b & c) = (a | b) & (a | c)
2.3 在编程中的应用
2.3.1 权限控制(位掩码)
在 Linux 系统或业务系统中,常使用位掩码(bitmask)表示用户权限。 原理:
读 = 001 (1)
写 = 010 (2)
执行 = 100 (4)
组合权限:
读写 = 读 | 写 = 001 | 010 = 011 (3)
读写执行 = 读 | 写 | 执行 = 111 (7)
代码示例 (Python):
# 定义权限标志
READ_PERMISSION = 0b001 # 1
WRITE_PERMISSION = 0b010 # 2
EXECUTE_PERMISSION = 0b100 # 4
# 为用户添加权限
def add_permission(user_perms, new_perms):
return user_perms | new_perms
user_perms = 0
user_perms = add_permission(user_perms, READ_PERMISSION) # 0b001
user_perms = add_permission(user_perms, WRITE_PERMISSION) # 0b011
# 检查权限 (配合 & 使用)
def has_permission(user_perms, check_perm):
return (user_perms & check_perm) == check_perm
print(has_permission(user_perms, READ_PERMISSION)) # True
print(has_permission(user_perms, EXECUTE_PERMISSION)) # False
2.3.2 实际监控场景
告警通知渠道配置(电话、邮件、短信)。
使用 OR 运算存储,节约空间,高效查询,避免使用数据库的 Like 查询,充分利用索引。
电话 = 1, 短信 = 2, 邮件 = 4
电话 + 短信 = 1 | 2 = 3
全渠道 = 1 | 2 | 4 = 7
2.3.3 多位数据的拼接(雪花算法)
如 64 位雪花算法 ID 的高效拼接。无需十进制相加,直接使用位移 << 和按位或 |。
代码示例 (Java):
public class SimpleSnowflake {
private static final long TIMESTAMP_BITS = 41L;
private static final long MACHINE_ID_BITS = 10L;
private static final long SEQUENCE_BITS = 12L;
public long generateId(long timestamp, long machineId, long sequence) {
// 技巧:使用 << 将各部分移动到指定位置,使用 | 将它们"拼"起来
return (timestamp << (MACHINE_ID_BITS + SEQUENCE_BITS))
| (machineId << SEQUENCE_BITS)
| sequence;
}
}
2.3.4 最接近的 2 的幂次方数
Java 8 中 HashMap 容量的计算,确保容量永远是 2 的幂次方。
代码示例 (Java):
public class CapacityCalculator {
// 例 new HashMap<>(7) 实际容量为 8
static final int tableSizeFor(int cap) {
int n = cap - 1;
// 技巧:通过连续的无符号右移和按位或,将最高位 1 后面的所有位都变为 1
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
// 此时 n 类似于 000111...,最后 +1 即可得到 001000... (即2的幂)
return n + 1;
}
}
3. 与 (&)
3.1 基本定义
与运算(Logical AND),符号通常为 &。
运算规则:全为 1 时结果为 1,只要有一个 0 结果即为 0。
真值表:
| A | B | A & B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
3.2 数学性质
- 交换律:
a & b = b & a - 结合律:
(a & b) & c = a & (b & c) - 幂等性:
a & a = a - 恒等性:
a & 1 = a(用于取最低位) - 支配性:
a & 0 = 0 - 分配律(对或运算):
a & (b | c) = (a & b) | (a & c)
3.3 在编程中的应用
3.3.1 检查是否 2 的幂(LeetCode 231)
原理:n & (n - 1)。如果 n 是 2 的幂次方,二进制最高位为 1 其余为 0;n-1 则最高位变为 0 其余变为 1。两者相与必为 0。
代码示例 (Python):
def is_power_of_two(n):
if n <= 0:
return False
return (n & (n - 1)) == 0
3.3.2 极其高效的“取模”运算
原理:X % N == X & (N - 1) (仅当 N 为 2 的幂时成立)。
在分库分表、Hash 计算、负载均衡中广泛使用。
代码示例 (Java):
// 假设 Map 的容量是 16 (2的幂)
int capacity = 16;
int hash = "user_id_123".hashCode();
// 普通写法
int index1 = hash % capacity;
// 高手写法 (位运算比取模快,且避免了负数 hash 取模的问题)
int index2 = hash & (capacity - 1);
3.3.3 IP 网段的判断
判断一个 IP 是否属于某个 CIDR 网段。 原理:IP 地址和子网掩码(Mask)本质都是 32 位整数。
代码示例 (Java 风格):
// 判断 IP: 192.168.1.55 是否在 192.168.1.0/24 网段内
int ip = parseIp("192.168.1.55");
int mask = parseIp("255.255.255.0");
int network = parseIp("192.168.1.0");
// 计算该 IP 的网络号
if ((ip & mask) == network) {
return true; // 在白名单网段内
}
4. 反 (~)
4.1 基本定义
按位翻转取反(NOT)。 真值表:
~0 = 1~1 = 0
4.2 在编程中的应用
4.2.1 权限移除
| 用于添加权限,& 用于检查权限,而 ~ 配合 & 则是移除权限的标准写法。
代码示例 (Java):
public class PermissionSystem {
private static final int PERMISSION_READ = 1 << 0; // 1
private static final int PERMISSION_WRITE = 1 << 1; // 2
private static final int PERMISSION_DELETE = 1 << 2; // 4
public static void main(String[] args) {
// 1. 初始化权限:拥有 读、写、删除 (111)
int userPermissions = PERMISSION_READ | PERMISSION_WRITE | PERMISSION_DELETE;
// 2. 业务需求:剥夺用户的“删除”权限
// 技巧:使用 &= ~MASK
// 逻辑:~PERMISSION_DELETE 生成一个"删除位"为0,其余全为1的掩码
userPermissions &= ~PERMISSION_DELETE;
// 结果:userPermissions 变为 011 (只剩读和写)
}
}
总结
作为后端开发人员,掌握位运算不仅能帮助我们解决特定的算法题,更能指导我们设计出更紧凑的数据结构和更高效的系统逻辑。
- 异或 (
^):用于加密、校验、无变量交换、数据去重。 - 或 (
|):用于状态叠加、权限组合、数据拼接。 - 与 (
&):用于状态检测、掩码提取、取模优化、数值性质判断。 - 反 (
~):用于状态移除、反转逻辑。