Skip to content

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结果
000
010
100
111
  • 示例

    java
    int a = 5; // 二进制: 0000 0101
    int b = 3; // 二进制: 0000 0011
    System.out.println(a & b); // 输出 1 (0000 0001)

2. 按位或 |

  • 用途:设置某一位为 1,合并两个数的位
  • 规则:对应位只要有一个为 1,结果就为 1
位1位2结果
000
011
101
111
  • 示例

    java
    int a = 5; // 0101
    int b = 3; // 0011
    System.out.println(a | b); // 输出 7 (0111)

3. 按位异或 ^

  • 用途:比较两个数的对应位差异、加密解密、交换变量
  • 规则:对应位不同为 1,相同为 0。
位1位2结果
000
011
101
110
  • 特性

    • a ^ a = 0:任何数与自身异或,结果为 0(所有位相同)
    • a ^ 0 = a:任何数与 0 异或,结果不变(0 的所有位为 0)
    • a ^ b ^ a = b:异或运算满足交换律和结合律,可用于恢复原值,可用于无临时变量交换两个数
  • 示例

    java
    int a = 5; // 0101
    int b = 3; // 0011
    System.out.println(a ^ b); // 输出 6 (0110)

    交换两个数的示例

    java
    int 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

  • 示例

    java
    int a = 5; // 0000 0101
    System.out.println(~a); // 输出 -6(二进制:1111 1010,补码表示)

5. 左移 <<

  • 相当于 乘以 2 的 n 次方(可能溢出)

  • 示例

    java
    int 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 次方(向下取整)

  • 示例

    java
    int a = -8; // 补码: 1111 1000
    System.out.println(a >> 1); // -4 ('1'111 1100) 注意符号位补1

7. 无符号右移 >>>(逻辑右移)

  • 高位一律补 0,结果恒为非负

  • 示例

    java
    int a = -1; // 32 位全为 1
    System.out.println(a >>> 1); // 2147483647 (0111...1111)

三、常见应用场景

1. 状态压缩(Flags)

用一个整数的每一位表示一个布尔状态:

java
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; // 0111

2. 快速乘除 2 的幂

java
x << 3   // 等价于 x * 8
x >> 2   // 等价于 x / 4(向下取整)

3. 判断奇偶性

java
if ((x & 1) == 1) {
    // x 是奇数
}

4. 交换两个整数(技巧,慎用)

java
a ^= b; // a = a^b
b ^= a; // b = 原 a
a ^= b; // a = 原 b

5. 算法优化(如 LeetCode - 36. 有效的数独)

int 的低 9 位记录数字 1–9 是否出现:

java
int row = 0;
int num = 5;

// 标记数字 5 已出现
row |= (1 << num);

// 检查数字 5 是否已存在
if ((row & (1 << num)) != 0) {
    // 存在!
}
  • 解释:
    • row 的低9 位记录数字 1–9 是否出现,初始化为全0

    • row |= (1 << num) 拆分为 2 个步骤:

      1. 1 << num:将1左移num位,生成一个只有第num位为1的数二进制形式为 ... 0001 0000
      2. row |= resrow = row | (1 << num)row与生成的数进行按位或运算,将第num位置为1
    • (row & (1 << num)) != 0 按位与运算检查该数字是否已出现


四、注意事项

  1. 运算符优先级低:务必使用括号

    ✅ 正确:(x & mask) != 0

    ❌ 错误:x & mask == 0== 优先级更高)

    运算符优先级(从高到低)
    ==, !=
    &
    ^
    |
    &&, ||

    这能直观解释为什么 (x & mask) == 0 必须加括号。

  2. 仅适用于整数类型byteshortintlong 浮点数(float/double不能进行位运算。

  3. 负数使用补码表示:理解 ~>>>>> 在负数下的行为差异。


五、速查表

操作表达式
判断第 n 位是否为 1(x & (1 << n)) != 0
将第 n 位设为 1x | = (1 << n)
将第 n 位清零x &= ~(1 << n)
翻转第 nx ^= (1 << n)
乘以 2ⁿx << n
除以 2ⁿ(向下取整)x >> n

掌握位运算,在某些场景下,能提升代码效率,写出更优雅的解决方案。

上次更新于: