Appearance
简介:
移位运算符就是在二进制的基础上对数字平移。按照平移的方向和填充数字的规则分为三种:<<(左移)、>>(带符号右移)和>>>(无符号右移)。
作用:
对于大数据的二进制运算,位移运算符比那些普通运算符的运算要快。因为程序仅仅移动而已,不去计算,这样提高了效率,节省了资源。比如这里:int newCapacity = oldCapacity + (oldCapacity >> 1); 右移一位相当于除2,右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。
一、三个运算符的规则
以 int 型 8 的二进制 0000...1000 为例(省略高位 0):
| 运算符 | 名称 | 规则 | 示例 | 结果 |
|---|---|---|---|---|
| << | 左移 | 右侧补 0 | 8 << 1 | 16(不溢出时等于 ×2) |
| >> | 算术右移(带符号) | 左侧补符号位 | 8 >> 2 | 2(正数左侧补 0) |
| >>> | 无符号右移 | 左侧一律补 0 | -8 >>> 28 | 15(负数变正数) |
核心区别在左移之后空出来的高位补什么:>> 补符号位(保住正负号),>>> 无脑补 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 后是 -562、计算大容量必须用 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 扩充(原内容过薄)。