核心概念

Java 提供三种移位运算符,用于对整型(intlong)的二进制位进行移动操作:

运算符名称行为符号位
<<左移二进制位左移,低位补 0可能改变
>>有符号右移二进制位右移,高位补符号位值保留
>>>无符号右移二进制位右移,高位始终补 0不保留(变正)

移位运算符的操作数只能是整型byteshortintlongchar)。对 byte/short/char 操作时,会先提升为 int


左移 <<

规则

value << n
  • value 的二进制位整体左移 n 位
  • 左侧溢出的高位丢弃
  • 右侧低位补 0
  • 等效于:乘以 2ⁿ(在不溢出的前提下)

示例

int a = 5;   // 二进制:0000 0000 0000 0000 0000 0000 0000 0101
int b = a << 2;  // 结果:20
                 // 二进制:0000 0000 0000 0000 0000 0000 0001 0100
int a = -5;  // 二进制:1111 1111 1111 1111 1111 1111 1111 1011(补码)
int b = a << 2;  // 结果:-20

有符号右移 >>

规则

value >> n
  • value 的二进制位整体右移 n 位
  • 右侧溢出的低位丢弃
  • 左侧高位补符号位的值(正数补 0,负数补 1)
  • 等效于:除以 2ⁿ 后向下取整(向负无穷方向)

示例

正数情况:高位补 0

int a = 20;  // 二进制:0000 0000 0000 0000 0000 0000 0001 0100
int b = a >> 2;  // 结果:5
                 // 二进制:0000 0000 0000 0000 0000 0000 0000 0101

负数情况:高位补 1

int a = -20; // 二进制:1111 1111 1111 1111 1111 1111 1110 1100
int b = a >> 2;  // 结果:-5
                 // 二进制:1111 1111 1111 1111 1111 1111 1111 1011

>> 保留符号位——负数右移后仍然是负数。

>> 与除法 ÷ 的区别

 20 >> 2  =  5      20 / 4 =  5   ✅
-20 >> 2  = -5     -20 / 4 = -5   ✅(可整除时一致)

 -1 >> 1  = -1      -1 / 2 =  0   ❌(不可整除时不同)
 -3 >> 1  = -2      -3 / 2 = -1   ❌

原因:>>向下取整(floor),而 Java 的整数除法是向零取整(truncation toward zero)。


无符号右移 >>>

规则

value >>> n
  • value 的二进制位整体右移 n 位
  • 右侧溢出的低位丢弃
  • 左侧高位始终补 0(无论正负)
  • 不保留符号位,负数右移后变成正数

示例

int a = -20;
// 二进制:1111 1111 1111 1111 1111 1111 1110 1100

int b = a >>> 2;
// 结果:1073741819
// 二进制:0011 1111 1111 1111 1111 1111 1111 1011

常见用途

  • 处理无符号语义的数值,如 int 作为位图使用
  • HashMap 中计算哈希扰动:h = key.hashCode() ^ (h >>> 16)
  • Arrays.binarySearch 中的中点计算:(low + high) >>> 1 (防止溢出)

(low + high) >>> 1 相比 (low + high) / 2 的优势

low + high 超过 int 最大值(2³¹ - 1)时,会溢出成负数,此时 /2 得到错误结果,而 >>> 1 把溢出的结果当作无符号数处理,得到正确的中间位置。JDK 源码中广泛使用此写法。


移位距离的取模规则

规则

Java 规范规定:移位距离 n 会对操作数的位数取模后再执行移位。

操作数类型实际移位距离取值范围
int(32 位)n & 0x1F(即 n % 320 ~ 31
long(64 位)n & 0x3F(即 n % 640 ~ 63

示例

int a = 1;
a << 32;   // 等效于 1 << 0 = 1(不移动)
a << 33;   // 等效于 1 << 1 = 2
a >> 32;   // 等效于 1 >> 0 = 1

踩坑提醒

如果写 1 << 32,你以为会得到 0(所有位移出),实际上因为取模规则,等于 1 << 0,结果仍然是 1。


三大运算符对比

操作5 << 220 >> 2-20 >> 2-20 >>> 2
结果205-51073741819
符号位可能变保留保留变正
高位补0符号位符号位0
数学含义× 2ⁿ÷ 2ⁿ(向下取整)÷ 2ⁿ(向下取整)无符号右移

常见应用场景

1. ArrayList 扩容 —— >>

int newCapacity = oldCapacity + (oldCapacity >> 1);  // 扩容 1.5 倍

oldCapacity >> 1 = oldCapacity / 2,相加得到 oldCapacity * 1.5。用移位代替除法,性能更高

详见 扩容机制详解(ArrayList)

2. HashMap 哈希扰动 —— >>>

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

将高 16 位与低 16 位异或,让高位信息参与低位运算,降低哈希碰撞概率

3. 二分查找中点计算 —— >>>

int mid = (low + high) >>> 1;  // 防溢出

4. 位图 / 权限掩码 —— <<

int READ    = 1 << 0;  // 0001 = 1
int WRITE   = 1 << 1;  // 0010 = 2
int EXECUTE = 1 << 2;  // 0100 = 4
 
int permission = READ | WRITE;  // 0011,拥有读写权限
boolean canWrite = (permission & WRITE) != 0;  // true

5. 颜色通道提取 —— >>>&

int pixel = 0xAABBCCDD;
int alpha = (pixel >>> 24) & 0xFF;  // 0xAA
int red   = (pixel >>> 16) & 0xFF;  // 0xBB
int green = (pixel >>> 8)  & 0xFF;  // 0xCC
int blue  = (pixel)        & 0xFF;  // 0xDD

性能说明

移位运算直接在 CPU 寄存器级别完成,是最快的整数运算之一:

运算周期数(约)
a * 23~5 周期(乘法)
a << 11 周期(移位)

现代 JIT 编译器会自动优化 n * 2n << 1n / 2n >> 1,但显式使用移位运算符可以提高代码的可读性,表明”我是在做位运算,不是算术”。


面试要点

  1. >>>>> 的区别>> 保留符号位(正补0负补1),>>> 强制补 0
  2. 移位替代乘除法<< 1 等效 ×2,>> 1 等效 ÷2(向下取整),性能更优
  3. 取模陷阱int 移位距离对 32 取模,1 << 32 = 1 而不是 0
  4. >>> 1 防溢出:二分查找中 (low + high) >>> 1 防止整数溢出
  5. ArrayList 扩容old + (old >> 1) = ×1.5 倍扩容

参考链接