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

Dod*_*ion 10 java collections java-9

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

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

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

现在输入Java 9:

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

这也很简洁,但遗憾的是,每次调用它时,它都会在堆上分配一个新的Set,然后立即将其用于垃圾回收.

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

private static final Set<String> SMALL_NUMBERS = Set.of(...);
Run Code Online (Sandbox Code Playgroud)

但是,这个定义可能距离大类中的方法定义一千行,并且您可能无法想到一个好的描述性名称,而文字可能更清晰(在这个假设的情况下).

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

Zhe*_*lov 15

我精心设计了一个简单的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

与运行基准测试后-prof gc,我可以得出如下结论:JIT优化list1,list2,set1,set2,但不list3,list4,set3,set4 [1]

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

List 2个元素的实现:

static final class List2<E> extends AbstractImmutableList<E> {
    private final E e0;
    private final E e1;
    ...
}
Run Code Online (Sandbox Code Playgroud)

List 实现3个或更多元素:

static final class ListN<E> extends AbstractImmutableList<E> {
    private final E[] elements;
    ...
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

  • 这并不令人惊讶.Hashing只能为足够大的集合付出代价,但由于你还在测量构造集合的成本(散列*所有*值),所以很可能看不到`Set.of(values).contains(...)`更快比`List.of(values).contains(...)`.如果JVM为这些工厂方法实现缓存而不是像任何其他临时对象那样进行标量化,那将会改变. (3认同)