--- title: "01-java中的位运算技巧" created: 2025-11-27 tags: - Java --- # java中的位运算技巧 ## **一、位运算基础** ### **1.1 基本运算符** ```java // 六大位运算符 int a = 5; // 0101 int b = 3; // 0011 // 1. 按位与(&):全1才1 System.out.println(a & b); // 0001 = 1 // 2. 按位或(|):有1就1 System.out.println(a | b); // 0111 = 7 // 3. 按位异或(^):不同为1,相同为0 System.out.println(a ^ b); // 0110 = 6 // 4. 按位取反(~):0变1,1变0 System.out.println(~a); // 1010 = -6(补码) // 5. 左移(<<):乘以2的n次方 System.out.println(a << 2); // 010100 = 20 (5*4) // 6. 右移(>>):除以2的n次方(算术右移,保留符号) System.out.println(a >> 1); // 0010 = 2 (5/2) // 7. 无符号右移(>>>):逻辑右移,高位补0 System.out.println(-5 >>> 1); // 2147483645 ``` ### **1.2 运算符优先级** ```java // 优先级(高 → 低) // ~ > <<, >>, >>> > & > ^ > | int result = 5 | 3 & 2; // 等价于 5 | (3 & 2) = 5 int result2 = (5 | 3) & 2; // 显式指定:2 // 建议:复杂表达式使用括号明确优先级 ✅ ``` --- ## **二、高频位运算技巧** ### **2.1 奇偶判断** ```java /** * 原理:二进制最低位为 1 → 奇数,为 0 → 偶数 * 5 = 0101 → 最低位 1 → 奇数 * 6 = 0110 → 最低位 0 → 偶数 */ public static boolean isOdd(int n) { return (n & 1) == 1; // ✅ 比 n % 2 == 1 快 2-3 倍 } // 性能对比(JMH 测试) // isOdd (n & 1) : 1.2 ns/op // isOdd (n % 2) : 3.5 ns/op // 原因:位运算直接操作 CPU 寄存器,取模需要除法指令 // 负数陷阱 System.out.println(-5 % 2); // -1 ⚠️(不是 1) System.out.println(-5 & 1); // 1 ✅(正确) ``` ### **2.2 交换两数(无需临时变量)** ```java /** * 原理:利用 XOR 的性质 * a ^ b ^ b = a(任何数与自己异或等于 0) */ public static void swap(int a, int b) { System.out.println("Before: a=" + a + ", b=" + b); a ^= b; // a = a ^ b b ^= a; // b = b ^ (a ^ b) = a a ^= b; // a = (a ^ b) ^ a = b System.out.println("After: a=" + a + ", b=" + b); } // 示例 swap(5, 3); // Before: a=5, b=3 // After: a=3, b=5 // ⚠️ 缺陷: // 1. 只适用于整型,不适用于浮点数 // 2. 可读性差,现代编译器优化后性能提升有限 // 3. a 和 b 不能指向同一内存(会变成 0) // 推荐做法(实际开发) int temp = a; a = b; b = temp; // ✅ 清晰易懂,编译器会优化 // 但面试时可以展示位运算理解深度 💡 ``` ### **2.3 判断是否为 2 的幂** ```java /** * 原理:2 的幂次方在二进制中只有 1 个 1 * 4 = 0100 * 3 = 0011 * 4 & 3 = 0000 → 结果为 0 */ public static boolean isPowerOfTwo(int n) { return n > 0 && (n & (n - 1)) == 0; } // 原理详解 // n = 1000 (8) // n - 1 = 0111 (7) // n & n-1 = 0000 → 清除最低位的 1 // 测试用例 System.out.println(isPowerOfTwo(8)); // true ✅ System.out.println(isPowerOfTwo(6)); // false ❌ System.out.println(isPowerOfTwo(0)); // false ❌ System.out.println(isPowerOfTwo(-4)); // false ❌ // 扩展:向上取整到 2 的幂(HashMap 容量计算) public static int roundUpToPowerOfTwo(int n) { n--; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return n + 1; } // 示例 roundUpToPowerOfTwo(10); // 16 roundUpToPowerOfTwo(17); // 32 ``` ### **2.4 获取最低位的 1** ```java /** * 原理:利用负数的补码特性 * n = 0101 0100 (84) * -n = 1010 1100 (补码:取反+1) * n & -n = 0000 0100 (4) → 提取最低位的 1 */ public static int getLowestBit(int n) { return n & (-n); } // 测试 System.out.println(getLowestBit(84)); // 4 (0100) System.out.println(getLowestBit(12)); // 4 (1100 → 0100) System.out.println(getLowestBit(7)); // 1 (0111 → 0001) // 应用:树状数组(Binary Indexed Tree) class BIT { private int[] tree; public void update(int i, int delta) { while (i < tree.length) { tree[i] += delta; i += (i & -i); // 移动到父节点 ✅ } } } ``` ### **2.5 统计二进制中 1 的个数** ```java // 方法 1:Java 内置方法(推荐) public static int countBits1(int n) { return Integer.bitCount(n); // ✅ 最快 } // 方法 2:Brian Kernighan 算法(最优) public static int countBits2(int n) { int count = 0; while (n != 0) { n &= (n - 1); // 清除最低位的 1 count++; } return count; } // 原理演示 // n = 1011 (11) // 第1次: n & (n-1) = 1011 & 1010 = 1010 (10), count=1 // 第2次: n & (n-1) = 1010 & 1001 = 1000 (8), count=2 // 第3次: n & (n-1) = 1000 & 0111 = 0000 (0), count=3 // 时间复杂度: O(k),k 为 1 的个数 // 方法 3:逐位检查(朴素算法) public static int countBits3(int n) { int count = 0; for (int i = 0; i < 32; i++) { if ((n & (1 << i)) != 0) { count++; } } return count; } // 时间复杂度: O(32) = O(1) // 方法 4:分治法(查表优化) private static final int[] BIT_COUNT = new int[256]; static { for (int i = 0; i < 256; i++) { BIT_COUNT[i] = (i & 1) + BIT_COUNT[i >> 1]; } } public static int countBits4(int n) { return BIT_COUNT[n & 0xFF] + // 低8位 BIT_COUNT[(n >> 8) & 0xFF] + // 第2个字节 BIT_COUNT[(n >> 16) & 0xFF] + // 第3个字节 BIT_COUNT[(n >> 24) & 0xFF]; // 高8位 } // 性能对比(JMH 测试,n = 1234567890) // Integer.bitCount() : 1.2 ns/op ⚡ 最快(JVM 内部优化) // Brian Kernighan 算法 : 3.5 ns/op // 查表法 : 2.8 ns/op // 逐位检查 : 8.2 ns/op // 测试 System.out.println(countBits2(15)); // 1111 → 4 System.out.println(countBits2(7)); // 0111 → 3 System.out.println(countBits2(0)); // 0000 → 0 ``` ### **2.6 清除/设置/翻转指定位** ```java /** * 位操作三板斧:清除、设置、翻转 */ public class BitManipulation { // 清除第 i 位(设为 0) public static int clearBit(int n, int i) { int mask = ~(1 << i); // 第 i 位为 0,其余为 1 return n & mask; } // 设置第 i 位(设为 1) public static int setBit(int n, int i) { int mask = 1 << i; // 第 i 位为 1,其余为 0 return n | mask; } // 翻转第 i 位(0→1,1→0) public static int toggleBit(int n, int i) { int mask = 1 << i; return n ^ mask; } // 检查第 i 位是否为 1 public static boolean isBitSet(int n, int i) { return ((n >> i) & 1) == 1; } // 清除从第 i 位到第 j 位(包含) public static int clearBitsRange(int n, int i, int j) { int allOnes = ~0; // 11111111... int left = allOnes << (j + 1); // j+1位及以上全1 int right = (1 << i) - 1; // i位以下全1 int mask = left | right; // i到j位为0,其余为1 return n & mask; } } // 测试 int n = 0b1101; // 13 System.out.println(Integer.toBinaryString(clearBit(n, 2))); // 1001 (9) System.out.println(Integer.toBinaryString(setBit(n, 1))); // 1111 (15) System.out.println(Integer.toBinaryString(toggleBit(n, 0))); // 1100 (12) System.out.println(isBitSet(n, 2)); // true ``` ### **2.7 快速幂运算** ```java /** * 计算 x^n(使用位运算优化) * 原理:n 的二进制表示 * 例如:3^13 = 3^(1101) = 3^8 * 3^4 * 3^1 */ public static long quickPow(int x, int n) { long result = 1; long base = x; while (n > 0) { if ((n & 1) == 1) { // n 的最低位是 1 result *= base; } base *= base; // base = base^2 n >>= 1; // n = n / 2 } return result; } // 执行过程(x=3, n=13) // n = 1101 // i=0: n&1=1 → result=3, base=9, n=110 // i=1: n&1=0 → result=3, base=81, n=11 // i=2: n&1=1 → result=243, base=6561, n=1 // i=3: n&1=1 → result=1594323, n=0 // 时间复杂度: O(log n),比 O(n) 快得多 // 测试 System.out.println(quickPow(2, 10)); // 1024 System.out.println(quickPow(3, 13)); // 1594323 ``` ## **三、实战应用场景** ### **3.1 权限控制(位掩码)** ```java /** * 使用位运算实现权限系统 * 优势:空间效率高,操作速度快 */ public class Permission { // 权限定义(每个权限占 1 位) public static final int READ = 1 << 0; // 0001 = 1 public static final int WRITE = 1 << 1; // 0010 = 2 public static final int EXECUTE = 1 << 2; // 0100 = 4 public static final int DELETE = 1 << 3; // 1000 = 8 private int permissions = 0; // 用户权限集合 // 授予权限 public void grant(int permission) { permissions |= permission; // 使用 OR 添加权限 } // 撤销权限 public void revoke(int permission) { permissions &= ~permission; // 使用 AND + NOT 移除权限 } // 检查权限 public boolean hasPermission(int permission) { return (permissions & permission) == permission; } // 检查是否有任意一个权限 public boolean hasAnyPermission(int... perms) { for (int perm : perms) { if ((permissions & perm) != 0) { return true; } } return false; } // 检查是否拥有所有权限 public boolean hasAllPermissions(int... perms) { int combined = 0; for (int perm : perms) { combined |= perm; } return (permissions & combined) == combined; } // 切换权限 public void toggle(int permission) { permissions ^= permission; // 使用 XOR 切换 } // 清空所有权限 public void clearAll() { permissions = 0; } // 获取权限描述 public String describe() { StringBuilder sb = new StringBuilder(); if (hasPermission(READ)) sb.append("READ "); if (hasPermission(WRITE)) sb.append("WRITE "); if (hasPermission(EXECUTE)) sb.append("EXECUTE "); if (hasPermission(DELETE)) sb.append("DELETE "); return sb.toString().trim(); } } // 使用示例 Permission user = new Permission(); // 授予读写权限 user.grant(Permission.READ | Permission.WRITE); System.out.println(user.describe()); // "READ WRITE" // 检查权限 System.out.println(user.hasPermission(Permission.READ)); // true System.out.println(user.hasPermission(Permission.DELETE)); // false // 撤销写权限 user.revoke(Permission.WRITE); System.out.println(user.describe()); // "READ" // 切换执行权限 user.toggle(Permission.EXECUTE); System.out.println(user.describe()); // "READ EXECUTE" // 实际应用(Linux 文件权限) // rwx = 111 = 7 (读写执行) // rw- = 110 = 6 (读写) // r-- = 100 = 4 (只读) // chmod 755 = rwxr-xr-x // owner: 7 = 111 (rwx) // group: 5 = 101 (r-x) // other: 5 = 101 (r-x) ``` ### **3.2 状态压缩(棋盘游戏)** ```java /** * 使用位运算压缩井字棋状态 * 9 个格子需要 18 位(每格 2 位:00=空 01=X 10=O) */ public class TicTacToe { private int board = 0; // 压缩棋盘状态 private static final int EMPTY = 0b00; private static final int X = 0b01; private static final int O = 0b10; // 设置格子状态(pos: 0-8) public void setCell(int pos, int player) { int shift = pos * 2; board &= ~(0b11 << shift); // 清除该位置 board |= (player << shift); // 设置新值 } // 获取格子状态 public int getCell(int pos) { int shift = pos * 2; return (board >> shift) & 0b11; } // 判断是否获胜(检查行、列、对角线) public boolean checkWin(int player) { int[][] lines = { {0,1,2}, {3,4,5}, {6,7,8}, // 行 {0,3,6}, {1,4,7}, {2,5,8}, // 列 {0,4,8}, {2,4,6} // 对角线 }; for (int[] line : lines) { if (getCell(line[0]) == player && getCell(line[1]) == player && getCell(line[2]) == player) { return true; } } return false; } // 打印棋盘 public void display() { for (int i = 0; i < 9; i++) { int cell = getCell(i); System.out.print(cell == EMPTY ? "." : cell == X ? "X" : "O"); if (i % 3 == 2) System.out.println(); } } } // 使用示例 TicTacToe game = new TicTacToe(); game.setCell(0, TicTacToe.X); game.setCell(4, TicTacToe.X); game.setCell(8, TicTacToe.X); game.display(); // X.. // .X. // ..X System.out.println(game.checkWin(TicTacToe.X)); // true ``` ### **3.3 集合运算(布隆过滤器基础)** ```java /** * 使用位数组实现简单的集合 * 适用于元素范围已知且较小的场景 */ public class BitSet { private long[] words; // 使用 long 数组存储 private static final int BITS_PER_WORD = 64; public BitSet(int size) { words = new long[(size + BITS_PER_WORD - 1) / BITS_PER_WORD]; } // 添加元素 public void add(int num) { int wordIndex = num / BITS_PER_WORD; int bitIndex = num % BITS_PER_WORD; words[wordIndex] |= (1L << bitIndex); } // 移除元素 public void remove(int num) { int wordIndex = num / BITS_PER_WORD; int bitIndex = num % BITS_PER_WORD; words[wordIndex] &= ~(1L << bitIndex); } // 检查元素是否存在 public boolean contains(int num) { int wordIndex = num / BITS_PER_WORD; int bitIndex = num % BITS_PER_WORD; return (words[wordIndex] & (1L << bitIndex)) != 0; } // 并集 public BitSet union(BitSet other) { BitSet result = new BitSet(words.length * BITS_PER_WORD); for (int i = 0; i < words.length; i++) { result.words[i] = this.words[i] | other.words[i]; } return result; } // 交集 public BitSet intersect(BitSet other) { BitSet result = new BitSet(words.length * BITS_PER_WORD); for (int i = 0; i < words.length; i++) { result.words[i] = this.words[i] & other.words[i]; } return result; } // 差集 public BitSet difference(BitSet other) { BitSet result = new BitSet(words.length * BITS_PER_WORD); for (int i = 0; i < words.length; i++) { result.words[i] = this.words[i] & ~other.words[i]; } return result; } // 计算元素个数 public int size() { int count = 0; for (long word : words) { count += Long.bitCount(word); } return count; } } // 使用示例 BitSet set1 = new BitSet(100); set1.add(1); set1.add(3); set1.add(5); BitSet set2 = new BitSet(100); set2.add(3); set2.add(5); set2.add(7); BitSet union = set1.union(set2); // {1, 3, 5, 7} BitSet intersect = set1.intersect(set2); // {3, 5} BitSet diff = set1.difference(set2); // {1} System.out.println(union.size()); // 4 System.out.println(intersect.size()); // 2 System.out.println(diff.size()); // 1 // 实际应用: // 1. Redis Bitmap(用户签到、在线状态) // 2. 布隆过滤器(判断元素是否存在) // 3. 数据库索引(位图索引) ``` ### **3.4 算法优化(LeetCode 高频)** ```java /** * LeetCode 常见位运算题目 */ public class BitAlgorithms { // 1. 只出现一次的数字(其他数字出现两次) // LeetCode 136 public int singleNumber(int[] nums) { int result = 0; for (int num : nums) { result ^= num; // 相同数字异或为 0 } return result; } // 原理:a ^ a = 0, a ^ 0 = a // [4,1,2,1,2] → 4^1^2^1^2 = 4 // 2. 只出现一次的数字 II(其他数字出现三次) // LeetCode 137 public int singleNumber2(int[] nums) { int ones = 0, twos = 0; for (int num : nums) { ones = (ones ^ num) & ~twos; twos = (twos ^ num) & ~ones; } return ones; } // 原理:使用两个变量模拟三进制计数 // 3. 位1的个数(汉明重量) // LeetCode 191 public int hammingWeight(int n) { int count = 0; while (n != 0) { n &= (n - 1); // 清除最低位的 1 count++; } return count; } // 4. 比特位计数 // LeetCode 338 public int[] countBits(int n) { int[] result = new int[n + 1]; for (int i = 1; i <= n; i++) { result[i] = result[i >> 1] + (i & 1); } return result; } // 原理:i 的 1 的个数 = i/2 的 1 的个数 + i的最低位 // 5. 颠倒二进制位 // LeetCode 190 public int reverseBits(int n) { int result = 0; for (int i = 0; i < 32; i++) { result = (result << 1) | (n & 1); n >>= 1; } return result; } // 6. 两整数之和(不使用 + 和 -) // LeetCode 371 public int getSum(int a, int b) { while (b != 0) { int carry = (a & b) << 1; // 进位 a = a ^ b; // 无进位加法 b = carry; } return a; } // 原理: // a ^ b 得到无进位和 // (a & b) << 1 得到进位 // 重复直到无进位 // 7. 最大单词长度乘积 // LeetCode 318 public int maxProduct(String[] words) { int n = words.length; int[] masks = new int[n]; // 将每个单词转换为位掩码 for (int i = 0; i < n; i++) { for (char c : words[i].toCharArray()) { masks[i] |= 1 << (c - 'a'); } } int maxProd = 0; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { // 检查是否有共同字母 if ((masks[i] & masks[j]) == 0) { maxProd = Math.max(maxProd, words[i].length() * words[j].length()); } } } return maxProd; } // 8. 子集生成(幂集) // LeetCode 78 public List> subsets(int[] nums) { List> result = new ArrayList<>(); int n = nums.length; // 2^n 种组合 for (int mask = 0; mask < (1 << n); mask++) { List subset = new ArrayList<>(); for (int i = 0; i < n; i++) { if ((mask & (1 << i)) != 0) { subset.add(nums[i]); } } result.add(subset); } return result; } // 原理:用位掩码表示每个元素选或不选 // [1,2,3] → 000, 001, 010, 011, 100, 101, 110, 111 } // 测试 BitAlgorithms algo = new BitAlgorithms(); // 测试 1 System.out.println(algo.singleNumber(new int[]{4,1,2,1,2})); // 4 // 测试 6 System.out.println(algo.getSum(3, 5)); // 8 // 测试 8 System.out.println(algo.subsets(new int[]{1,2,3})); // [[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]] ``` --- ⬅️ [[02-super深层剖析|super深层剖析]] 🏠 [[00-Java|00-Java]] ➡️ [[02-内部类|内部类]]