一、前置知识

Java 中 byteshortintlong 整数底层都是以 补码 形式存储。
位运算直接操作二进制比特位,执行速度碾压普通加减乘除、取模运算,大量底层源码(HashMapConcurrentHashMap、JDK 工具类)随处可见。

Java 7 种核心位运算符:
& 按位与、| 按位或、^ 异或、~ 取反、<< 左移、>> 算术右移、>>> 无符号右移


二、七大位运算符逐类详解

1. & 按位与

1.1 运算规则

同位二进制:都为 1,结果才是 1;只要有一个是 0,结果就是 0

5: 0101
3: 0011
& ------
   0001  = 1

1.2 核心场景详细讲解

场景1:哈希表寻址(HashMap 底层核心)

  • 常规取模:hash % capacity
  • 位运算替代:hash & (capacity - 1)

原理
容量必须是 2 的整数次幂,此时 capacity-1 二进制低位全是 1
按位与直接保留 hash 值低位,效果和取模一模一样;
位运算只需 1 个 CPU 周期,取模除法要十几倍周期,效率极高。

// 模拟 HashMap 桶下标计算
int hash = 12345;
int capacity = 16;
int index = hash & (capacity - 1);
System.out.println("桶下标:" + index);

举例推演:十进制 25 → 二进制 11001。要对 8 取模,8 的二进制是 100025 % 8 的含义是取 250-7 的映射,也就是取 11001 的后三位(000-111)。此时把桶大小设置为 2 的 n 次方,则 (桶大小-1) 正好是 111。用 11001 & 0111,对每一位进行与运算,最后结果是 001,转为十进制为 1,正好是 25 % 8 的结果。这样就不用取模除法了,只需要进行多位位运算。

场景2:判断数字奇偶

  • 常规写法:x % 2 == 1
  • 位运算写法:(x & 1) == 1(奇数,否则偶数)

原理
二进制数字最低位为 1 一定是奇数,为 0 一定是偶数。& 1 只保留最后一位,其余全部置 0,直接判断奇偶,效率更高。

public static boolean isOdd(int num){
    return (num & 1) == 1;
}

场景3:掩码保留低位数据

  • 需求:只保留一个数最后 4 位,其余高位清零。
  • 写法:num & 0xF

原理
0xF 二进制是 1111,按位与规则:高位和 0 相与全部清零,低 4 位和 1 相与保留原值,起到掩码过滤作用。

int num = 0x1234;
int last4 = num & 0xF;
System.out.println("保留最后4位:" + last4);

避坑区分
&& 是逻辑短路与,用于条件判断;
& 既是位运算按位与,也可以做逻辑与但不短路


2. | 按位或

2.1 运算规则

同位二进制:有 1 就是 1;只有全 0 才是 0

5: 0101
3: 0011
| ------
   0111  = 7

2.2 核心场景详细讲解

场景1:HashMap#tableSizeFor 凑 2 的幂

一、官方原生源码

/**
 * 返回大于等于 cap 的最小 2 的整数次幂
 */
public static final int tableSizeFor(int cap) {
    // 第一步:容量先减 1
    int n = cap - 1;
    // 核心五行位运算:把最高位1后面全部填成1
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    // 边界容错,最终 +1 得到 2 的幂
    return (n < 0) ? 1 : (n >= Integer.MAX_VALUE >>> 1) ? Integer.MAX_VALUE >>> 1 : n + 1;
}

作用:输入一个期望容量 cap,返回大于等于 cap 的最小 2 的整数次幂。
比如:cap = 10 → 16cap = 16 → 16cap = 17 → 32

二、逐行拆解每一步原理

  1. int n = cap - 1;

    • 核心目的:如果传入的 cap 本身就是 2 的幂,先减 1,避免最后算出下一个 2 的幂。
    • 举例:cap = 16(本身是 2⁴)。不减 1 后续会变成 31,+1 → 32(错);先减 1 变成 15,后续全填 1 还是 15,最后 +1 → 16(对)。
  2. 五行位运算核心

    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    
    • 作用:把二进制最高位的 1,向右不断「传染」,把后面所有低位全部变成 1
    • 为什么是 1、2、4、8、16? int 固定 32 位,按倍增右移,一轮就能覆盖全部 32 个二进制位,无遗漏。
  3. 返回值逻辑

    return (n < 0) ? 1 : (n >= Integer.MAX_VALUE >>> 1) ? Integer.MAX_VALUE >>> 1 : n + 1;
    
    • n < 0:传入负数容量,直接返回最小容量 1
    • 容量过大超过阈值,返回 HashMap 最大容量
    • 正常情况:n + 1。前面已经把低位全填 1,全 1 的数 +1 正好进位成 2 的整数次幂。

三、手把手二进制举例推演(以 cap = 10 为例)

  1. n = 10 - 1 = 9 → 二进制 0000 1001
  2. n |= n >>> 1 → 右移1位 0000 0100,或运算后 0000 1101
  3. n |= n >>> 2 → 右移2位 0000 0011,或运算后 0000 1111
  4. 后面 >>>4、>>>8、>>>16 运算后,数值不变,依旧 0000 1111(15)
  5. 最后 n + 1 = 16 → 得到大于 10 的最小 2 的幂:16

一句话总结整个算法流程

  1. 先减 1:规避本身就是 2 的幂的情况
  2. 五次无符号右移 + 按位或:把最高位 1 后面所有位全部刷成 1
  3. 最后加 1:低位全 1 进位,刚好变成最近的 2 的整数次幂
  4. 边界判断:处理负数、超大容量兜底

场景2:权限位合并设计
定义权限:读(1)、写(2)、删(4)
原理:每一种权限占用一个二进制位,用按位或合并多个权限,一个 int 就能存多种权限。

// 权限定义
int READ = 1;
int WRITE = 2;
int DELETE = 4;

// 同时拥有读+写权限
int auth = READ | WRITE;
// 判断是否拥有删除权限
boolean hasDelete = (auth & DELETE) == DELETE;

场景3:默认值兜底赋值
低位默认补 1,快速给数值设置默认标记位,底层状态初始化常用。

// 初始状态全为0
int status = 0;

// 默认兜底:初始化、可用、就绪 三位默认置1
final int DEFAULT_STATUS = 0B111;

// 一行兜底赋值
status |= DEFAULT_STATUS;
System.out.println(status); // 7  二进制 111

3. ^ 按位异或

3.1 运算规则

同位二进制:相同为 0,不同为 1

5: 0101
3: 0011
^ ------
   0110  = 6

3.2 神仙特性

  1. a ^ a = 0
  2. a ^ 0 = a
  3. 满足交换律、结合律

3.3 核心场景详细讲解

场景1:无中间变量交换两个整数

a ^= b;
b ^= a;
a ^= b;

原理:利用 a^a=0a^0=a 特性,通过异或互相抵消,不借助第三方变量完成交换。

场景2:HashMap 哈希扰动
一、原生核心扰动源码

/**
 * HashMap 哈希扰动方法:高低位异或打散
 */
static final int hash(Object key) {
    int h;
    // 核心:原hash值 ^ 原hash无符号右移16位
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

原理:HashMap 找桶下标是 hash & (容量-1)。容量默认 16、32、64……只有低位生效,高 16 位直接被丢弃。如果不扰动,高 16 位再怎么变,桶下标都一样,严重哈希碰撞!int 32 位,高 16 位变化少、低 16 位变化多;用 ^ 把高低位混合,让高位特征下沉到低位,减少哈希碰撞。

场景3:简单加密、数据去重
相同数字异或抵消为 0,重复数据自动清零;也可固定一个密钥,和原文异或做简单加密解密。利用 a^a=0a^0=a 特性。


4. ~ 按位取反

4.1 运算规则

一元运算符,所有二进制位:0110
补码公式:~x = -x - 1
示例:~5 = -6

4.2 核心场景详细讲解

场景1:快速数值取反偏移
利用公式 ~x+1 等价于相反数,底层源码快速正负转换(转化后的是补码)。

场景2:清除标记位、JDK状态重置
二进制标记位置 1001,快速清空状态位、重置标志,集合、线程状态底层大量使用。
JDK 底层标准写法【只清空指定某一个状态位】:实际源码不会直接无脑 ~ 全部位,而是:掩码 + ~ 取反 + & 与运算,精准只清某一位,其他位不动。

public static void main(String[] args) {
    // 状态定义
    final int INIT    = 1 << 0; // 0001 初始化
    final int CACHE   = 1 << 1; // 0010 缓存就绪
    final int READ_WRITE = 1 << 2; // 0100 允许读写

    // 当前三个状态全部开启:0111
    int state = INIT | CACHE | READ_WRITE;

    // 核心:~CACHE 把缓存位 0变1、1变0,再 & 运算,只清空缓存位,其他保留
    state = state & ~CACHE;

    // 结果:0100  只剩初始化、允许读写,缓存位被清空
    System.out.println(Integer.toBinaryString(state));
}

5. << 左移

5.1 运算规则

整体二进制左移 n 位,右侧补 0
数学等价:x << n = x * 2ⁿ
示例:2 << 3 = 16

5.2 核心场景详细讲解

场景1:快速生成 2 的整数次幂

1 << 4   // 16
1 << 8   // 256
1 << 16  // 65536

原理1 左移 n 位,等价 2n 次方,比 Math.pow 更快、更简洁。

场景2:替代乘法,极速运算
只要是乘以 24816… 都用左移替代;位运算 1 个时钟周期,乘法指令慢很多。
⚠️ 注意:左移会舍弃高位,超出范围直接溢出。


6. >> 算术右移

6.1 算术右移原理与 /2 深度区别

一、算术右移 >> 核心原理

  1. 底层基础前提:计算机中所有整数统一以补码存储。
    • 正数:原码 = 反码 = 补码,最高符号位为 0
    • 负数:补码 = 反码 + 1,最高符号位为 1
  2. 算术右移(>>)本质规则
    • 右侧溢出的低位:直接丢弃;
    • 左侧空出的高位:强制补符号位(正数补 0,负数补 1
    • 数学定义:x >> n = ⌊ x / 2ⁿ ⌋(固定为向下取整,向负无穷取整)

二、>>1 与 普通除法 /2 核心区别

对比维度 算术右移 >> 1 整数除法 / 2
取整规则 统一向下取整(向负无穷) 向 0 取整(截断小数)
正数示例 5 >> 1 = 2 5 / 2 = 2
负数示例 -5 >> 1 = -3 -5 / 2 = -2
CPU性能 硬件移位指令,1个周期 除法器复杂运算,10~30周期
代码逻辑 无需判正负,天然统一 负数不符合预期时需额外 if 修正

结论:业务需要正负统一减半时,>>1 无需判正负;JDK 容器扩容/阈值计算强制用 >>1 代替 /2,是工程层面的经典优化。

6.2 核心场景详细讲解

  • 场景1:正负快速整除。算术右移屏蔽了正负差异,一套逻辑适配所有整数减半,无需额外分支判断。
  • 场景2:JDK 集合阈值。容器频繁扩容、阈值刷新是高频操作,用 >>1 替代 /2,用极低的性能开销实现阈值减半。

7. >>> 无符号右移

7.1 运算规则

整体右移 n 位,左侧永远补 0,无视正负号,只纯操作二进制。

7.2 核心场景详细讲解

  • 场景1:HashMap 哈希扰动核心 h >>> 16
    原理:把 int 高 16 位,右移到低 16 位,和原数异或混合;不保留符号影响,纯打散二进制,大幅降低哈希碰撞概率。
  • 场景2:拆分 int 高低位
    把 32 位 int 拆成高 16 位、低 16 位,网络协议、二进制解析、底层通信常用无符号右移。

三、位运算符总览对照表

运算符 作用 等价数学运算 JDK 源码典型用途
& 留1去0、掩码过滤 快速取模 HashMap 桶下标定位
| 合并置1、多状态叠加 数值累加 tableSizeFor 凑2的幂
^ 同位相异、打散数据 归零、交换两数 哈希扰动、防碰撞
~ 所有位按位反转 -x-1 状态位清空、重置标记
<< 整体左移、低位补0 * 2ⁿ 快速生成容器容量
>> 算术右移、补符号位 向下取整 / 2ⁿ 扩容阈值减半
>>> 无符号右移、高位补0 纯二进制平分 高低位混合哈希

四、两大避坑重点

  1. 类型提升陷阱byte/short 做位运算,会自动提升为 int,运算结果一定是 int,赋值回原类型时需要手动强转。
  2. 性能碾压原理:乘法/除法/取模需要 CPU 专用运算单元(ALU 复杂逻辑),要 10~30 个时钟周期;位运算直接操作逻辑门/移位器,仅需 1 个时钟周期,底层性能碾压。在高频并发、底层框架开发中应优先使用。

五、实战源码整合演示

1. 模拟 HashMap tableSizeFor 凑 2 的幂

public static int tableSizeFor(int n) {
    n--;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : n + 1;
}

2. 位运算奇偶判断 + 哈希寻址

public class BitDemo {
    // 判断奇偶
    public static boolean isOdd(int num){
        return (num & 1) == 1;
    }

    // 哈希桶下标计算
    public static int getIndex(int hash, int capacity){
        return hash & (capacity - 1);
    }

    public static void main(String[] args) {
        System.out.println("15是奇数吗?" + isOdd(15));
        System.out.println("16是奇数吗?" + isOdd(16));
        System.out.println("Hash 789 在容量16下的桶下标:" + getIndex(789, 16));
    }
}

六、总结

位运算不是“奇技淫巧”,而是计算机底层数据处理的基石。掌握 &|^~<<>>>>> 的核心规则与适用场景,不仅能让你在阅读 JDK 源码、Netty、Spring 等框架底层时豁然开朗,更能在高并发、内存敏感型业务中写出极致性能的代码。建议结合 Integer.toBinaryString() 多动手打印验证,形成二进制直觉。

更多推荐