【Java】代码细节

166 阅读2分钟

记录一些 Java 代码中非常细节的点。有时候看看就行,没必要专门去记这个。

遍历列表的几种写法

遍历列表是一个非常频繁的操作,有很多种写法,但是不同写法之间还是有细微差异的。

常见写法

public static int sum1(List<Integer> list) {
    int res = 0;
    for (int i = 0; i < list.size(); i++)
        res += list.get(i);
    return res;
}

iMax 写法

public static int sum2(List<Integer> list) {
    int res = 0;
    for (int i = 0, iMax = list.size(); i < iMax; i++)
        res += list.get(i);
    return res;
}

看起来第一种相当于每一轮循环都会重新获取列表的大小,字节码的反映上确实是这样子的,这个是 sum1 的字节码:

 0: iconst_0
 1: istore_1
 2: iconst_0
 3: istore_2
 4: iload_2
 5: aload_0
 6: invokeinterface #2,  1 // InterfaceMethod java/util/List.size:()I
11: if_icmpge     36
14: iload_1
15: aload_0
16: iload_2
17: invokeinterface #3,  2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
22: checkcast     #4       // class java/lang/Integer
25: invokevirtual #5       // Method java/lang/Integer.intValue:()I
28: iadd
29: istore_1
30: iinc          2, 1
33: goto          4        // 重点
36: iload_1
37: ireturn

字节码显示每一轮循环结束都会跳转到第 4 条指令,而第 6 条指令负责调用 size() 方法获取列表大小,因此每一轮循环都要重新获取终止条件。

for-loop 写法

public void sum3(Blackhole blackhole) {
    int sum = 0;
    for (Integer integer : list)
        sum += integer;
    blackhole.consume(sum);
}

我们看一下 for-loop 写法编译后反编译的代码:

public void sum3(Blackhole blackhole) {
    int sum = 0;
    Integer integer;
    for(Iterator var3 = this.list.iterator(); var3.hasNext(); sum += integer)
        integer = (Integer)var3.next();
    blackhole.consume(sum);
}

可以看出,for-loop 写法实际上就是一个语法糖,本质上是循环的迭代器写法。使用迭代器的循环消耗肯定比前两种要高,但是好处是迭代过程中可以增删列表的元素而不会报错。

虽然普通写法的字节码显示会重复调用 List::size 方法,但是实际上数据量小时执行得要比其他写法快,应该是 JIT 的优化导致的。数据量大时 for-loop 写法要快些,估计也是 JIT 优化的原因。

# 10000 数据量
Benchmark           Mode  Cnt   Score   Error  Units
LoopBenchmark.sum1  avgt   10   8.954 ± 0.465  us/op
LoopBenchmark.sum2  avgt   10   9.656 ± 0.369  us/op
LoopBenchmark.sum3  avgt   10  10.304 ± 0.513  us/op

# 100000 数据量
Benchmark           Mode  Cnt    Score    Error  Units
LoopBenchmark.sum1  avgt   10  127.279 ± 39.001  us/op
LoopBenchmark.sum2  avgt   10  119.146 ± 15.773  us/op
LoopBenchmark.sum3  avgt   10  110.528 ±  2.728  us/op