实现Java集合迭代的高性能

举报
杰克 发表于 2018/12/03 15:19:29 2018/12/03
【摘要】 实现Java集合迭代的高性能一、介绍Java开发者经常会遇到处理集合(比如ArrayList、HashSet)的情况,Java 8也提供了Lambda表达式和Streaming API来简化集合相关的工作。在大多数应用场景下,无需考虑集合迭代的性能消耗。但是,在一些极端情况下,比如集合包含了上百万条记录的情况,这个时候集合迭代就需要选择正确的姿势,否则性能会较差。使用JMH检查下面每段代码片...

实现Java集合迭代的高性能

一、介绍

Java开发者经常会遇到处理集合比如ArrayList、HashSet的情况Java 8也提供了Lambda表达式和Streaming API来简化集合相关的工作。在大多数应用场景下无需考虑集合迭代的性能消耗。但是在一些极端情况下比如集合包含了上百万条记录的情况这个时候集合迭代就需要选择正确的姿势否则性能会较差。


使用JMH检查下面每段代码片段的运行时间。


二、forEach vs. C Style vs. Stream API

迭代是一个非常基本的功能所有的编程语言都有简单的迭代语法允许程序员在集合上运行迭代。Stream API可以通过Collections用非常直接的方式进行迭代。


public List<Integer> streamSingleThread(BenchMarkState state) {
    List<Integer> result = new ArrayList<>(state.testData.size());
    state.testData.stream().forEach(item -> {
        result.add(item);
    });
    return result;
}
public List<Integer> streamMultiThread(BenchMarkState state) {
    List<Integer> result = new ArrayList<>(state.testData.size());
    state.testData.stream().parallel().forEach(item -> {
        result.add(item);
    });
    return result;
}

使用forEach循环也非常简单


public List<Integer> forEach(BenchMarkState state) {
    List<Integer> result = new ArrayList<>(state.testData.size());
    for(Integer item : state.testData) {
        result.add(item);
    }
    return result;
}

C style方式的迭代其代码要冗长一些但仍然非常紧凑


public List<Integer> forCStyle(BenchMarkState state) {
    int size = state.testData.size();
    List<Integer> result = new ArrayList<>(size);
    for(int j = 0; j < size; j ++){
        result.add(state.testData.get(j));
    }
    return result;
}

以上代码的性能评分如下


Benchmark Mode Cnt Score Error Units

TestLoopPerformance.forCStyle avgt 200 18.068 ± 0.074 ms/op

TestLoopPerformance.forEach avgt 200 30.566 ± 0.165 ms/op

TestLoopPerformance.streamMultiThread avgt 200 79.433 ± 0.747 ms/op

TestLoopPerformance.streamSingleThread avgt 200 37.779 ± 0.485 ms/op


对于C style方式的迭代JVM只是简单地增加了一个整型变量它直接从内存读值。这使它非常快。但forEach迭代则不同根据Oracle官方文档JVM必须把forEach转换为迭代器并为每个数据项调用hasNext()。这就是为什么forEach比C style迭代慢。


forEach文档(https://docs.oracle.com/javase/1.5.0/docs/guide/language/foreach.html)


三、哪一种迭代的性能最高

我们定义测试数据


@State(Scope.Benchmark)
public static class BenchMarkState {
    @Setup(Level.Trial)
    public void doSetup() {
        for(int i = 0; i < 500000; i++){
            testData.add(Integer.valueOf(i));
        }
    }
    @TearDown(Level.Trial)
    public void doTearDown() {
        testData = new HashSet<>(500000);
    }
    public Set<Integer> testData = new HashSet<>(500000);
}

Java Set同时支持Stream API和forEach循环。根据前面的测试如果我们把Set转换为ArrayList看看性能是否有所提升。


public List<Integer> forCStyle(BenchMarkState state) {
    int size = state.testData.size();
    List<Integer> result = new ArrayList<>(size);
    Integer[] temp = (Integer[]) state.testData.toArray(new Integer[size]);
    for(int j = 0; j < size; j ++) {
        result.add(temp[j]);
    }
    return result;
}

C style组合迭代的循环


public List forCStyleWithIteration(BenchMarkState state) { 
int size = state.testData.size(); 
List result = new ArrayList<>(size); 
Iterator iteration = state.testData.iterator(); 
for(int j = 0; j < size; j ++) { 
result.add(iteration.next()); 
} 
return result; 
}

forEach


public List<Integer> forEach(BenchMarkState state) {
    List<Integer> result = new ArrayList<>(state.testData.size());
    for(Integer item : state.testData) {
        result.add(item);
    }
    return result;
}

看起来代码简洁但并不理想因为初始化ArrayList比较消耗资源。


Benchmark Mode Cnt Score Error Units

TestLoopPerformance.forCStyle avgt 200 6.013 ± 0.108 ms/op

TestLoopPerformance.forCStyleWithIteration avgt 200 4.281 ± 0.049 ms/op

TestLoopPerformance.forEach avgt 200 4.498 ± 0.026 ms/op

HashMap (HashSet uses HashMap


结论

在集合Collections上使用Foreach和Stream API是非常便利的方法写这样的代码也显得精炼。但要记住当系统需要考虑性能和稳定性因素时就应该改写这些循环。


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。