问题
如何以最高效率计算 2 乘以 8?
答案
一、核心答案
最高效的方式是使用位运算:2 << 3(左移 3 位)
int result = 2 << 3; // 结果为 16,等价于 2 * 8
原理: 左移 n 位相当于乘以 2^n,2 << 3 即 2 * 2³ = 2 * 8 = 16
二、原理详解
1. 位运算的本质
二进制表示:
2 的二进制: 0000 0010
左移 3 位: 0001 0000 (相当于在末尾补 3 个 0)
结果: 16(十进制)
数学推导:
2 << 1 = 2 * 2¹ = 4
2 << 2 = 2 * 2² = 8
2 << 3 = 2 * 2³ = 16
通用公式: a << n = a * 2^n
2. 性能对比
| 方式 | 代码 | CPU 指令 | 性能 |
|---|---|---|---|
| 位运算 | 2 << 3 | 1 条移位指令(SHL) | 最快 |
| 普通乘法 | 2 * 8 | 1 条乘法指令(IMUL) | 较快 |
| 循环���加 | for (int i=0; i<8; i++) sum+=2 | 多条指令 | 最慢 |
基准测试:
public class PerformanceTest {
private static final int ITERATIONS = 100_000_000;
public static void main(String[] args) {
// 测试位运算
long start1 = System.nanoTime();
for (int i = 0; i < ITERATIONS; i++) {
int result = 2 << 3;
}
long end1 = System.nanoTime();
System.out.println("位运算耗时: " + (end1 - start1) / 1_000_000 + "ms");
// 测试普通乘法
long start2 = System.nanoTime();
for (int i = 0; i < ITERATIONS; i++) {
int result = 2 * 8;
}
long end2 = System.nanoTime();
System.out.println("普通乘法耗时: " + (end2 - start2) / 1_000_000 + "ms");
}
}
实际结果: 在现代 JVM 中,两者性能几乎相同(JIT 编译器会自动优化)
三、JVM 编译器优化
1. JIT 编译器的自动优化
// 源代码
int result = 2 * 8;
// JIT 编译后的字节码(简化表示)
// 编译器会自动识别乘以 2 的幂次,优化为位移操作
int result = 2 << 3;
验证方式: 使用 -XX:+PrintAssembly 查看汇编代码
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly YourClass
2. 编译器优化的条件
| 场景 | 是否优化 | 说明 |
|---|---|---|
2 * 8 | ✅ 是 | 常量乘法,编译期优化 |
x * 8 | ✅ 是 | 变量乘以 2 的幂次,JIT 优化 |
x * 7 | ❌ 否 | 非 2 的幂次,使用乘法指令 |
x * y | ❌ 否 | 两个变量相乘,无法优化 |
四、位运算的其他应用
1. 常见位运算优化
// 乘以 2 的幂次
int a = x * 2; → int a = x << 1;
int b = x * 4; → int b = x << 2;
int c = x * 16; → int c = x << 4;
// 除以 2 的幂次
int d = x / 2; → int d = x >> 1;
int e = x / 4; → int e = x >> 2;
// 取模运算(仅适用于 2 的幂次)
int f = x % 8; → int f = x & 7; // 7 = 8 - 1 = 0b0111
2. 实际应用场景
场景一:HashMap 容量计算
// HashMap 源码中的位运算优化
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
场景二:判断奇偶性
// 传统方式
boolean isEven = (x % 2 == 0);
// 位运算优化
boolean isEven = (x & 1) == 0; // 最低位为 0 则为偶数
场景三:权限校验(位掩码)
// 权限定义
int READ = 1 << 0; // 0001
int WRITE = 1 << 1; // 0010
int EXEC = 1 << 2; // 0100
// 授予权限
int permission = READ | WRITE; // 0011
// 检查权限
boolean canRead = (permission & READ) != 0; // true
boolean canExec = (permission & EXEC) != 0; // false
五、注意事项与陷阱
1. 溢出问题
int a = Integer.MAX_VALUE;
int result = a << 1; // ❌ 溢出,结果为 -2
// 解决方案:使用 long 类型
long result = ((long) a) << 1; // ✅ 正确
2. 负数的右移
int x = -8; // 二进制:1111 1111 1111 1111 1111 1111 1111 1000
// 算术右移(>>):保留符号位
int a = x >> 2; // 结果:-2(1111 1111 1111 1111 1111 1111 1111 1110)
// 逻辑右移(>>>):高位补 0
int b = x >>> 2; // 结果:1073741822(0011 1111 1111 1111 1111 1111 1111 1110)
3. 可读性 vs 性能
// ❌ 过度优化,降低可读性
int area = (width << 1) + (height << 1);
// ✅ 推荐写法(编译器会自动优化)
int area = 2 * width + 2 * height;
原则: 在非性能关键路径上,优先保证代码可读性
六、现代 JVM 的优化策略
1. 常量折叠(Constant Folding)
// 源代码
int result = 2 * 8;
// 编译器直接计算结果
int result = 16;
2. 强度削减(Strength Reduction)
// 源代码
for (int i = 0; i < n; i++) {
int result = i * 8;
}
// 优化后
int temp = 0;
for (int i = 0; i < n; i++) {
int result = temp;
temp += 8; // 乘法转换为加法
}
七、答题总结
面试回答要点:
- 直接答案:使用位运算
2 << 3,左移 3 位相当于乘以 2³ = 8 - 原理说明:
- 左移 n 位 = 乘以 2^n
- 位运算直接操作二进制,只需 1 条 CPU 指令
- 性能对比:
- 位运算:1 条移位指令(SHL)
- 普通乘法:1 条乘法指令(IMUL)
- 现代 JVM 中两者性能相近(JIT 自动优化)
- 实际应用:
- HashMap 容量计算
- 权限位掩码
- 奇偶性判断
- 注意事项:
- 避免溢出(使用 long 类型)
- 优先保证可读性(编译器会自动优化)
- 仅在性能关键路径手动优化
关键记忆点:
a << n=a * 2^na >> n=a / 2^n- 现代 JVM 会自动将
x * 8优化为x << 3 - 过度优化会降低代码可读性,应权衡取舍