Skip to content

简介: ​

移位运算符就是在二进制的基础上对数字平移。按照平移的方向和填充数字的规则分为三种:<<(左移)、>>(带符号右移)和>>>(无符号右移)。

作用: ​

对于大数据的二进制运算,位移运算符比那些普通运算符的运算要快。因为程序仅仅移动而已,不去计算,这样提高了效率,节省了资源。比如这里:int newCapacity = oldCapacity + (oldCapacity >> 1); 右移一位相当于除2,右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。

一、三个运算符的规则 ​

以 int 型 8 的二进制 0000...1000 为例(省略高位 0):

运算符名称规则示例结果
<<左移右侧补 08 << 116(不溢出时等于 ×2)
>>算术右移(带符号)左侧补符号位8 >> 22(正数左侧补 0)
>>>无符号右移左侧一律补 0-8 >>> 2815(负数变正数)

核心区别在左移之后空出来的高位补什么:>> 补符号位(保住正负号),>>> 无脑补 0(符号信息丢掉)。

二、负数移位与补码 ​

Java 的整数用补码存储:负数的补码 = 按位取反再加 1。以 8 位示意(实际 int 是 32 位):

 +8 : 0000 1000
 -8 : 1111 1000          (8 取反 0111 0111,加 1 得 1111 1000)
-8 >> 1  : 1111 1100 = -4   左侧补符号位 1,仍是负数,向下取整
-8 << 1  : 1111 0000 = -16  向左移动不影响"补 0"规则
-8 >>> 3 : 0001 1111 = 31   左侧补 0,符号丢了,负数变正数

两个高频结论:

  • x >> n 对负数是向负无穷方向取整:-7 >> 1 == -4,而 -7 / 2 == -3(除法向零取整),二者结果不同(见第四节)。
  • >>> 多用于哈希扰动和掩码:不关心符号、只想把高位信息混进低位,如 HashMap 的 (h = key.hashCode()) ^ (h >>> 16)、tableSizeFor() 里的连环 n |= n >>> 1。

三、移位位数先取模 ​

移位位数不是任意的,JVM 会先对操作数位数取模:

  • int(32 位):移位数对 32 取模,所以 x << 35 等价于 x << 3,x << 32 等于 x 本身(不是 0!);
  • long(64 位):移位数对 64 取模,且移位运算会先把左侧操作数提升为 int/long。
java
System.out.println(1 << 32);   // 1,32 % 32 == 0,等于没移(不是 0 也不是 2147483648)
System.out.println(1 << 35);   // 8,35 % 32 == 3
System.out.println(1L << 35);  // 34359738368,long 按 64 取模,正常左移

四、"移位比乘除快"的真实语义 ​

1、语义等价是有条件的

x << k 等价于 x * 2^k 只在不溢出时成立;x >> k 近似 x / 2^k 但舍入方向不同(见第二节 -7 的例子)。把除法换成右移,等于悄悄改了负数的舍入行为。

2、JVM 层面:编译器/JIT 会自动做这个优化

i * 8 在编译期常量折叠、运行期 JIT 都会编译成 i << 3 或乘法指令(x86 的 lea 指令连移位都不用)。手写移位通常换不来性能收益,却损失可读性。

3、实践结论

  • 业务代码写 *、/:语义清晰、舍入正确;
  • 移位留给这些场景。JDK/框架源码里的容量计算(ArrayList 扩容)、哈希扰动、位图/掩码运算——即读者预期出现位技巧的地方。

五、常见坑 ​

1、byte/short 会提升为 int

java
byte b = 100;
// b = b << 1;        // 编译错误:b << 1 已经是 int
b = (byte) (b << 1);  // 必须显式强转,且 200 溢出 byte 后是 -56

2、计算大容量必须用 long 字面量

java
int  wrong = 1 << 31;    // 溢出符号位,得到 Integer.MIN_VALUE(负数!)
long a = 1 << 40;        // 还是错:1 是 int,先按 32 位取模,结果被截断
long b = 1L << 40;       // 正确:1L 是 long,按 64 位移位

3、优先级低于加减法

java
1 << 2 + 3   // 等价于 1 << 5 = 32,不是 (1<<2)+3 = 7,加减先算

4、无符号右移不改变"已是正数"的事实

对正数而言 >> 与 >>> 结果相同,区别只在负数。反过来,别指望 >>> 能"修正"一个已经溢出成负数的值。它只会把负数变成一个巨大的正数。

延伸阅读:ArrayList扩容(1.5 倍扩容里的 >> 1)。HashMap 的长度为什么是2的幂次方(tableSizeFor 的连环 >>>)。HashMap源码注释(hash 扰动的 h >>> 16)。

2026-10 扩充(原内容过薄)。