Java 9 个集合的便利工厂方法作为集合文字的替代方法

Java 9 collections' convenience factory methods as an alternative to collection literals

考虑这种方法(仅供说明):

boolean isSmallNumber(String s) {
    return (n in ["one", "two", "three", "four"]);
}

当然,这不是 Java,但它可能是您最喜欢的支持集合文字的替代语言,例如 GroovyKotlin。表达式简洁,并且,就像字符串文字一样,允许编译器将集合文字放在某个静态存储区域(甚至 "intern()" 它)。

现在输入Java 9:

boolean isSmallNumber(String s) {
    return Set.of("one", "two", "three", "four").contains(s);
}

这也很简洁,但不幸的是,它会在您每次调用它时在堆上分配一个新的 Set,然后立即使其可用于垃圾回收。

你当然可以定义一个集合常量:

private static final Set<String> SMALL_NUMBERS = Set.of(...);

但是这个定义可能与方法定义相差一千行 class,您可能无法为它想出一个好的描述性名称,而文字可能更清晰 (在这种假设情况下)。

那么,如果我在方法内部使用 Set.of(...)JIT 编译器是否会在每次调用该方法时优化新对象的创建?

我制作了一个简单的 JMH 基准测试:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class Temp {

    private Object value;

    @Setup
    public void setUp() {
        value = 50;
    }

    @Benchmark
    public boolean list1() {
        return List.of("one").contains(value);
    }

    @Benchmark
    public boolean list2() {
        return List.of("one", "two").contains(value);
    }

    @Benchmark
    public boolean list3() {
        return List.of("one", "two", "three").contains(value);
    }

    @Benchmark
    public boolean list4() {
        return List.of("one", "two", "three", "four").contains(value);
    }

    @Benchmark
    public boolean set1() {
        return Set.of("one").contains(value);
    }

    @Benchmark
    public boolean set2() {
        return Set.of("one", "two").contains(value);
    }

    @Benchmark
    public boolean set3() {
        return Set.of("one", "two", "three").contains(value);
    }

    @Benchmark
    public boolean set4() {
        return Set.of("one", "two", "three", "four").contains(value);
    }
}

经过运行与-prof gc的benchmark后,我可以得出以下结论:JIT优化了list1list2set1set2,但不是 list3list4set3set4 [1]

这似乎完全合理,因为 N >= 3 listN/setNN <= 2 创建更复杂的 List/Set 实现。

List 2 个元素的实现:

static final class List2<E> extends AbstractImmutableList<E> {
    private final E e0;
    private final E e1;
    ...
}

List 3 个或更多元素的实现:

static final class ListN<E> extends AbstractImmutableList<E> {
    private final E[] elements;
    ...
}

ListN 包含另一个间接级别(一个数组),这显然使逃逸分析变得更加困难。


JMH 输出(略微更改以适应页面):

Benchmark                  Mode  Cnt     Score      Error   Units
list1                      avgt    5     3,075 ?    1,165   ns/op
list1:·gc.alloc.rate       avgt    5     0,131 ?    1,117  MB/sec
list1:·gc.alloc.rate.norm  avgt    5    ? 10??               B/op
list1:·gc.count            avgt    5       ? 0             counts

list2                      avgt    5     3,161 ?    0,543   ns/op
list2:·gc.alloc.rate       avgt    5     0,494 ?    3,065  MB/sec
list2:·gc.alloc.rate.norm  avgt    5     0,001 ?    0,003    B/op
list2:·gc.count            avgt    5       ? 0             counts

list3                      avgt    5    33,094 ?    4,402   ns/op
list3:·gc.alloc.rate       avgt    5  6316,970 ?  750,240  MB/sec
list3:·gc.alloc.rate.norm  avgt    5    64,016 ?    0,089    B/op
list3:·gc.count            avgt    5   169,000             counts
list3:·gc.time             avgt    5   154,000                 ms

list4                      avgt    5    32,718 ?    3,657   ns/op
list4:·gc.alloc.rate       avgt    5  6403,487 ?  729,235  MB/sec
list4:·gc.alloc.rate.norm  avgt    5    64,004 ?    0,017    B/op
list4:·gc.count            avgt    5   165,000             counts
list4:·gc.time             avgt    5   146,000                 ms

set1                       avgt    5     3,218 ?    0,822   ns/op
set1:·gc.alloc.rate        avgt    5     0,237 ?    1,973  MB/sec
set1:·gc.alloc.rate.norm   avgt    5    ? 10??               B/op
set1:·gc.count             avgt    5       ? 0             counts

set2                       avgt    5     7,087 ?    2,029   ns/op
set2:·gc.alloc.rate        avgt    5     0,647 ?    4,755  MB/sec
set2:·gc.alloc.rate.norm   avgt    5     0,001 ?    0,010    B/op
set2:·gc.count             avgt    5       ? 0             counts

set3                       avgt    5    88,460 ?   16,834   ns/op
set3:·gc.alloc.rate        avgt    5  3565,506 ?  687,900  MB/sec
set3:·gc.alloc.rate.norm   avgt    5    96,000 ?    0,001    B/op
set3:·gc.count             avgt    5   143,000             counts
set3:·gc.time              avgt    5   108,000                 ms

set4                       avgt    5   118,652 ?   41,035   ns/op
set4:·gc.alloc.rate        avgt    5  2887,359 ?  920,180  MB/sec
set4:·gc.alloc.rate.norm   avgt    5   104,000 ?    0,001    B/op
set4:·gc.count             avgt    5   136,000             counts
set4:·gc.time              avgt    5    94,000                 ms

[1] Java HotSpot(TM) 64 位服务器 VM(构建 9+181,混合模式)