Java 全部位运算符 超详细讲解(附源码实战场景)
Java 全部位运算符 超详细讲解(附源码实战场景)
一、前置知识
Java 中 byte、short、int、long 整数底层都是以 补码 形式存储。
位运算直接操作二进制比特位,执行速度碾压普通加减乘除、取模运算,大量底层源码(HashMap、ConcurrentHashMap、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的二进制是1000。25 % 8的含义是取25在0-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 → 16,cap = 16 → 16,cap = 17 → 32。
二、逐行拆解每一步原理
-
int n = cap - 1;- 核心目的:如果传入的
cap本身就是 2 的幂,先减 1,避免最后算出下一个 2 的幂。 - 举例:
cap = 16(本身是 2⁴)。不减 1 后续会变成 31,+1 → 32(错);先减 1 变成 15,后续全填 1 还是 15,最后 +1 → 16(对)。
- 核心目的:如果传入的
-
五行位运算核心
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 个二进制位,无遗漏。
- 作用:把二进制最高位的
-
返回值逻辑
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 为例)
n = 10 - 1 = 9→ 二进制0000 1001n |= n >>> 1→ 右移1位0000 0100,或运算后0000 1101n |= n >>> 2→ 右移2位0000 0011,或运算后0000 1111- 后面
>>>4、>>>8、>>>16运算后,数值不变,依旧0000 1111(15) - 最后
n + 1 = 16→ 得到大于 10 的最小 2 的幂:16
一句话总结整个算法流程
- 先减 1:规避本身就是 2 的幂的情况
- 五次无符号右移 + 按位或:把最高位 1 后面所有位全部刷成 1
- 最后加 1:低位全 1 进位,刚好变成最近的 2 的整数次幂
- 边界判断:处理负数、超大容量兜底
场景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 神仙特性
a ^ a = 0a ^ 0 = a- 满足交换律、结合律
3.3 核心场景详细讲解
场景1:无中间变量交换两个整数
a ^= b;
b ^= a;
a ^= b;
原理:利用 a^a=0、a^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=0、a^0=a 特性。
4. ~ 按位取反
4.1 运算规则
一元运算符,所有二进制位:0 变 1,1 变 0。
补码公式:~x = -x - 1
示例:~5 = -6
4.2 核心场景详细讲解
场景1:快速数值取反偏移
利用公式 ~x+1 等价于相反数,底层源码快速正负转换(转化后的是补码)。
场景2:清除标记位、JDK状态重置
二进制标记位置 1 变 0、0 变 1,快速清空状态位、重置标志,集合、线程状态底层大量使用。
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 位,等价 2 的 n 次方,比 Math.pow 更快、更简洁。
场景2:替代乘法,极速运算
只要是乘以 2、4、8、16… 都用左移替代;位运算 1 个时钟周期,乘法指令慢很多。
⚠️ 注意:左移会舍弃高位,超出范围直接溢出。
6. >> 算术右移
6.1 算术右移原理与 /2 深度区别
一、算术右移 >> 核心原理
- 底层基础前提:计算机中所有整数统一以补码存储。
- 正数:原码 = 反码 = 补码,最高符号位为
0 - 负数:补码 = 反码 + 1,最高符号位为
1
- 正数:原码 = 反码 = 补码,最高符号位为
- 算术右移(
>>)本质规则:- 右侧溢出的低位:直接丢弃;
- 左侧空出的高位:强制补符号位(正数补
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 | 纯二进制平分 | 高低位混合哈希 |
四、两大避坑重点
- 类型提升陷阱:
byte/short做位运算,会自动提升为int,运算结果一定是int,赋值回原类型时需要手动强转。 - 性能碾压原理:乘法/除法/取模需要 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() 多动手打印验证,形成二进制直觉。
更多推荐

所有评论(0)