Java 有哪些位运算符?>> 和 >>> 有什么区别?位运算有什么应用?
简化版
Java 的位运算符直接操作二进制位:&(与)、|(或)、^(异或)、~(取反) 是逻辑位运算;<<(左移)、>>(有符号右移/算术右移)、>>>(无符号右移/逻辑右移) 是移位运算。>> 和 >>> 的区别是右移时高位补什么:>> 补符号位(正数补 0、负数补 1,保持正负号),>>> 一律补 0(不管正负)——所以负数用 >>> 会变成一个很大的正数。位运算在 Java 里应用广泛:HashMap 用 hash & (n-1) 代替取模、h ^ (h>>>16) 扰动、权限用位掩码(一个 int 存 32 个开关)、^ 交换/找唯一数、<< 快速乘 2 等。注意:Java 没有无符号左移 <<<(左移不涉及符号位问题,只有一个 <<)。
详细版
逻辑位运算符:
int a = 0b1100, b = 0b1010; // 12 和 10
a & b // 0b1000 = 8 (与:都是 1 才 1)
a | b // 0b1110 = 14 (或:有一个 1 就 1)
a ^ b // 0b0110 = 6 (异或:不同为 1、相同为 0)
~a // 取反:每一位取反(~12 = -13,因为补码)
移位运算符:
int x = 8; // 0b1000
x << 1; // 16 左移 1 位 = ×2(低位补 0)
x >> 1; // 4 有符号右移 1 位 = ÷2(高位补符号位)
int neg = -8; // 补码 11111111...11111000
neg >> 1; // -4 有符号右移,高位补 1(补符号位,保持负数)
neg >>> 1; // 2147483644 无符号右移,高位补 0 → 变成很大的正数!
>> vs >>> 的核心区别:
>>(有符号/算术右移) | >>>(无符号/逻辑右移) | |
|---|---|---|
| 高位补什么 | 补符号位(正补 0、负补 1) | 一律补 0 |
| 正数右移 | 正常(补 0) | 正常(补 0)——正数两者一样 |
| 负数右移 | 保持负数(补 1) | 变成大正数(补 0) |
| 用途 | 保持符号的除以 2ⁿ | 处理无符号数、hash 扰动 |
Java 只有一个左移 <<(没有 <<<):
左移时低位补 0,不涉及符号位,所以不需要区分有符号/无符号
右移才需要区分(高位补什么关系到符号)→ 所以有 >> 和 >>>
⚠️ Java 的移位有个「位数取模」的坑:移位位数会对「类型的位数」取模。对 int(32 位),
x << 33实际是x << (33 % 32) = x << 1;对 long(64 位)是对 64 取模。所以1 << 32不是 0 而是1 << 0 = 1!移位位数超过类型位数时不会「移没了」,而是取模后重新算。这是容易踩的隐蔽坑。
完整版教学
一、位运算是什么:直接操作二进制位
位运算符直接对整数的二进制位进行操作,比普通的算术/逻辑运算更底层、更快:
Java 的位运算符分两类:
逻辑位运算(对每一位做布尔运算):
& 与:两位都是 1 才是 1
| 或:有一位是 1 就是 1
^ 异或:两位不同为 1、相同为 0
~ 取反:每一位取反(0变1、1变0)
移位运算(把二进制位整体移动):
<< 左移、>> 有符号右移、>>> 无符号右移
位运算的价值:① 高效(直接操作二进制,比乘除等快);② 能做「按位」的事(如用一个 int 的 32 个 bit 存 32 个开关);③ 底层框架大量用(HashMap、集合、加密、图形处理)。理解位运算,才能看懂 JDK 源码里的很多「位操作技巧」(如 HashMap 的 hash & (n-1))。理解「位运算直接操作二进制位、高效且能按位处理」,就理解了它的定位——它是「底层、高效、按位」的运算。
二、>> 和 >>> 的区别:高位补什么
>> 和 >>> 都是右移,区别在右移时高位补什么——这是位运算最高频的考点:
>>(有符号右移/算术右移):高位补"符号位"
正数:符号位是 0 → 高位补 0
负数:符号位是 1 → 高位补 1(保持负数)
→ 效果:保持符号,相当于"除以 2ⁿ 并向下取整"
>>>(无符号右移/逻辑右移):高位一律补 0(不管符号)
正数:补 0(和 >> 一样)
负数:补 0 → 符号位变 0 → 从负数变成很大的正数!
用负数看清区别:
-8 的补码:11111111 11111111 11111111 11111000
-8 >> 1(补符号位 1):
11111111 11111111 11111111 11111100 = -4 ✓ 保持负数(相当于 -8/2)
-8 >>> 1(补 0):
01111111 11111111 11111111 11111100 = 2147483644 ← 变成大正数!
关键区别:正数右移时 >> 和 >>> 结果一样(都补 0);负数右移时不同——>> 补 1 保持负数、>>> 补 0 变成大正数。>> 用于「保持符号的除法」,>>> 用于「把负数当无符号数处理」(如 hash 值的处理、取二进制的高位)。理解「>> 补符号位保持正负、>>> 补 0 负数变大正数」,就掌握了这道高频题的核心——记住「负数才有区别」。
三、为什么没有无符号左移 <<<
一个常被问的问题:Java 有 >> 和 >>> 两种右移,但左移只有 <<(没有 <<<),为什么?
右移为什么需要两种:
右移时,"高位"空出来要补什么?
补符号位(>>,保持符号)还是补 0(>>>,无符号)?
→ 高位补什么关系到"符号",所以需要两种右移区分
左移为什么只需要一种:
左移时,"低位"空出来,一律补 0(低位补 0 是唯一合理的选择)
→ 低位补 0 不涉及符号位(符号位在高位)
→ 所以左移不需要区分有符号/无符号,只有一个 <<
核心原因:移位时「空出来的位」补什么,只有右移的「高位」涉及符号(高位是符号位所在),左移的「低位」补 0 是唯一选择、不涉及符号。所以右移要区分 >>(补符号)和 >>>(补 0),左移只需 <<(低位补 0)。理解「左移低位补 0 不涉及符号所以只有一种、右移高位涉及符号所以有两种」,就理解了为什么没有 <<<——这是个能体现理解深度的追问。
四、位运算应用一:HashMap 的位运算
JDK 源码里位运算无处不在,最经典的是 HashMap 用位运算代替取模和做 hash 扰动:
// ① 用 hash & (n-1) 代替 hash % n(n 是容量,必为 2 的幂)
int index = hash & (n - 1); // 等价于 hash % n,但位运算更快
// 原理:n 是 2 的幂时,n-1 的二进制是"低位全 1"(如 16-1=0b1111)
// hash & 0b1111 = 保留 hash 的低 4 位 = hash % 16
// ② hash 扰动:h ^ (h >>> 16)
static int hash(Object key) {
int h = key.hashCode();
return h ^ (h >>> 16); // 把高 16 位异或到低 16 位,让高位也参与下标计算
}
两个位运算技巧:① hash & (n-1) 代替取模——当 n 是 2 的幂时,n-1 是「低位全 1」的掩码,hash & (n-1) 保留 hash 的低位、恰好等价于 hash % n,但位运算比取模快得多(这就是「HashMap 容量为什么是 2 的幂」的原因)。② h ^ (h >>> 16) 扰动——用 >>>(无符号右移,补 0)把 hash 的高 16 位移到低位,再异或,让高位信息也参与下标计算,减少哈希冲突(这里用 >>> 而非 >> 是因为要纯粹地取高位、不受符号干扰)。理解「HashMap 用 & 代替取模、用 ^ 和 >>> 做扰动」,就看懂了 JDK 里最经典的位运算应用。
五、位运算应用二:位掩码(权限/开关)
位运算能「用一个整数的每一位表示一个开关」——一个 int 有 32 位,能存 32 个布尔状态,这就是「位掩码」:
// 用位表示权限(每一位一个权限)
static final int READ = 1 << 0; // 0b001 = 1
static final int WRITE = 1 << 1; // 0b010 = 2
static final int EXECUTE = 1 << 2; // 0b100 = 4
int permission = 0;
permission |= READ; // 加权限:或运算置位 → 0b001
permission |= WRITE; // 再加 WRITE → 0b011
boolean canRead = (permission & READ) != 0; // 检查权限:与运算判断 → true
permission &= ~WRITE; // 去权限:与"取反的掩码" → 0b001(去掉 WRITE)
位掩码的操作套路(面试常考):① 加某位(置 1)用 |=(permission |= WRITE);② 查某位用 &((permission & WRITE) != 0);③ 去某位(清 0)用 &= ~(permission &= ~WRITE);④ 切换某位用 ^=。位掩码的好处是「用一个 int 紧凑地存多个开关、且操作高效」——比用一堆 boolean 字段省空间、比用 Set 省内存。Java 里 Modifier(反射的修饰符)、正则的 flag、很多框架的选项都用位掩码。理解「位掩码用一个整数存多个开关、|加 &查 &~去」,就掌握了位运算的这个实用场景。
六、位运算应用三:异或的妙用
异或(^) 有几个特殊性质,带来很多巧妙应用:
异或的性质:
a ^ a = 0 (相同为 0)
a ^ 0 = a (和 0 异或不变)
异或满足交换律和结合律
应用1:找出唯一不重复的数
数组里每个数出现两次,只有一个出现一次,找它:
把所有数异或起来 → 出现两次的都抵消成 0(a^a=0),剩下的就是那个唯一的数
应用2:不用临时变量交换两个数
a = a ^ b; b = a ^ b; a = a ^ b; // 交换 a 和 b(但实际不推荐,可读性差)
应用3:简单加密
数据 ^ 密钥 = 密文,密文 ^ 密钥 = 原数据(异或两次还原)
异或的核心性质是「a ^ a = 0、a ^ 0 = a」——「相同抵消、和 0 不变」。由此衍生:① 找唯一数(全部异或,成对的抵消,剩唯一的——LeetCode 经典题);② 交换变量(三次异或,但可读性差、不推荐实际用);③ 简单异或加密(异或两次还原);④ 判断两数符号是否相同((a ^ b) >= 0 表示同号)。理解「异或的相同抵消性质带来找唯一数、交换、加密等妙用」,就掌握了位运算里最灵活的运算符。(异或的算法应用在算法方向的位运算题里更详细。)
记忆钩子:「位运算符:& 与、| 或、^ 异或、~ 取反、<< 左移、>> 有符号右移(补符号位保持正负)、>>> 无符号右移(补 0,负数变大正数);没有 <<<(左移低位补 0 不涉及符号);移位位数对类型位数取模(1<<32 = 1<<0 = 1);应用:HashMap 的 hash&(n-1) 代替取模、h^(h>>>16) 扰动,位掩码 |加&查&~去,异或 a^a=0 找唯一数/交换/加密」。
七、常见误区与追问
- 误区:>> 和 >>> 结果总是一样。 只有正数右移时一样(都补 0);负数右移不同——>> 补符号位 1 保持负数、>>> 补 0 使负数变成很大的正数。
- 误区:Java 有无符号左移 <<<。 没有——左移时低位补 0 不涉及符号位,只需一个 <<;右移的高位补什么涉及符号,所以才有 >> 和 >>>。
- 误区:
1 << 32等于 0(移没了)。 等于 1——移位位数会对类型位数取模,int 是 32 位,1 << 32=1 << (32%32)=1 << 0= 1。 - 误区:位运算只是底层技巧、实际用不到。 JDK 源码大量使用——HashMap 的取模和扰动、位掩码权限、集合的位图、正则 flag 等,理解它才能读懂源码。
- 追问:>> 和 >>> 的区别? 右移时高位补什么——>>(有符号/算术右移)补符号位(正补 0、负补 1,保持符号),>>>(无符号/逻辑右移)一律补 0(负数会变成很大的正数);正数时两者相同。
- 追问:为什么 Java 没有 <<<? 左移时空出的是低位、一律补 0(唯一合理选择),不涉及符号位(符号位在高位),所以左移不需区分有符号/无符号;只有右移的高位补什么涉及符号,才需要 >> 和 >>>。
- 追问:HashMap 里
hash & (n-1)和h ^ (h>>>16)分别干什么?hash & (n-1)(n 是 2 的幂)等价于hash % n但更快(n-1 是低位全 1 的掩码);h ^ (h>>>16)是扰动函数,用无符号右移把高 16 位异或到低位,让高位参与下标计算、减少冲突。
八、加强记忆
Java 位运算符直接操作二进制位:逻辑位运算 &(与)、|(或)、^(异或)、~(取反);移位运算 <<(左移)、>>(有符号/算术右移)、>>>(无符号/逻辑右移)。>> 和 >>> 的区别是右移时高位补什么——>> 补符号位(正补 0、负补 1,保持正负号),>>> 一律补 0(负数会变成很大的正数),正数右移时两者相同(关键:只有负数才有区别)。没有 <<<——左移低位补 0 不涉及符号,只有右移高位补什么涉及符号才需两种。坑:移位位数对类型位数取模(int 32 位,1<<32 = 1<<0 = 1,不是 0)。三大应用:① HashMap 用 hash & (n-1) 代替取模(n 是 2 的幂时 n-1 是低位全 1 掩码)、h ^ (h>>>16) 扰动(高位参与下标);② 位掩码用一个 int 存多个开关(|= 加、& 查、&= ~ 去);③ 异或(a^a=0、a^0=a)找唯一数/交换/加密。一句话「& | ^ ~ 逻辑位运算、<< >> >>> 移位,>> 补符号位 >>> 补 0(负数才有区别),无 <<<,移位位数取模,HashMap 用 &(n-1) 和 ^(h>>>16),位掩码存开关,异或找唯一数」。