问题

如何以最高效率计算 2 乘以 8?

答案

一、核心答案

最高效的方式是使用位运算:2 << 3(左移 3 位)

int result = 2 << 3;  // 结果为 16,等价于 2 * 8

原理: 左移 n 位相当于乘以 2^n,2 << 32 * 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;  // 乘法转换为加法
}

七、答题总结

面试回答要点:

  1. 直接答案:使用位运算 2 << 3,左移 3 位相当于乘以 2³ = 8
  2. 原理说明
    • 左移 n 位 = 乘以 2^n
    • 位运算直接操作二进制,只需 1 条 CPU 指令
  3. 性能对比
    • 位运算:1 条移位指令(SHL)
    • 普通乘法:1 条乘法指令(IMUL)
    • 现代 JVM 中两者性能相近(JIT 自动优化)
  4. 实际应用
    • HashMap 容量计算
    • 权限位掩码
    • 奇偶性判断
  5. 注意事项
    • 避免溢出(使用 long 类型)
    • 优先保证可读性(编译器会自动优化)
    • 仅在性能关键路径手动优化

关键记忆点:

  • a << n = a * 2^n
  • a >> n = a / 2^n
  • 现代 JVM 会自动将 x * 8 优化为 x << 3
  • 过度优化会降低代码可读性,应权衡取舍