Stream.of(item).collect(Collectors.toSomeCollection())比新的SomeCollection(Arrays.asList(item))更快吗?

时间:2018-01-28 12:19:03

标签: java collections java-stream

Java 8提供

input()

e.g。 Stream.of(item).collect(Collectors.toSomeCollection()) 。避免相对昂贵的Stream.of("abc").collect(Collectors.toSet())操作员在幕前比

更快
new

e.g。 new SomeCollection(Arrays.asList(item))

我确定使用new HashSet<>(Arrays.asList("abc"))初始化不同集合的成本不同(哈希集在准备使用之前需要哈希表,数组列表是已分配的数组,而链表不需要这样)。

我试图弄清楚流相关类是否在内部避免new,但OpenJDK代码很难理解。我认为如果我将new视为已经初始化的管道和使用可重用函数的收集器,我们就可以。

可能是另一种方式吗?

3 个答案:

答案 0 :(得分:8)

让我们衡量(使用jmh)并找出哪一个更快&#39;:

@BenchmarkMode({ Mode.AverageTime })
@Warmup(iterations = 10)
@Measurement(iterations = 10)
@Fork(1)
public class MyBenchmark {

    private static final int ITERATIONS = 10_000_000;

    @Benchmark
    public void baseLine(Blackhole bh) {
        for (int i = 0; i < ITERATIONS; i++) {
            Set<String> s = new HashSet<>();
            s.add("A");
            bh.consume(s);
        }
    }

    @Benchmark
    public void asList(Blackhole bh) {
        for (int i = 0; i < ITERATIONS; i++) {
            Set<?> s = new HashSet<>(Arrays.asList("A"));
            bh.consume(s);
        }
    }

    @Benchmark
    public void collectStream(Blackhole bh) {
        for (int i = 0; i < ITERATIONS; i++) {
            Set<?> s = Stream.of("A").collect(Collectors.toSet());
            bh.consume(s);
        }
    }

    @Benchmark
    public void setOf(Blackhole bh) {
        for (int i = 0; i < ITERATIONS; i++) {
            Set<?> s = Set.of("A");
            bh.consume(s);
        }
    }

    @Benchmark
    public void singletonCollection(Blackhole bh) {
        for (int i = 0; i < ITERATIONS; i++) {
            Set<?> s = Collections.singleton("A");
            bh.consume(s);
        }
    }

}

结果是:

# JMH version: 1.19
# VM version: JDK 9, VM 9+181

Benchmark                        Mode  Cnt  Score   Error  Units
MyBenchmark.baseLine             avgt   10  0.301 ± 0.002   s/op
MyBenchmark.asList               avgt   10  0.350 ± 0.012   s/op    
MyBenchmark.collectStream        avgt   10  0.517 ± 0.009   s/op
MyBenchmark.setOf                avgt   10  0.057 ± 0.001   s/op
MyBenchmark.singletonCollection  avgt   10  0.057 ± 0.001   s/op

所以在你提到的2中,使用asList似乎更快。如果您只有一个元素,则可以使用Collections.singleton*(由@AndyTurner建议)。 Java 9接口工厂方法适用于任意数量的元素,但也针对单个元素进行了优化。

答案 1 :(得分:2)

理论上,Stream.of(item).collect(Collectors.toSomeCollection())可以返回更有效的集合,因为故意未指定确切的集合类型。

在实践中,有一些阻碍这种情况的障碍。例如。收集器必须provide a supplier容器才能积累元素,而不提供有关流特征的任何提示,即使它们事先已知,例如Stream.of(item)示例,已知其具有单个元素。收集器可以为中间类型提供供应商,该类型在最后转换为不同的结果类型,这可以增加内存效率,但需要额外的CPU时间。

这会导致例如Arrays.asList(stream.toArray())可能比stream.collect(Collectors.toList())效率更高,因为如果可预测,toArray()操作可以访问流 size 。但是对于通过Stream.of(item[,items])创建的没有中间操作的流,显然不能比Arrays.asList(item[,items])更有效率,List在没有流机制的情况下首先创建Collections.singletoList(item)

但是对于单个项目,List.of(item)或Java 9的Set甚至更高效,因为它们根本不需要处理数组。

创建HashSet时,差异甚至更大,正如Jorn Vernee’s answer中的实际数字所示。

要理解它们,我们必须意识到在每个“baseline”,“asList”和“collectStream”变体中,都会创建一个HashSet。即使在当前的Java 9中,HashMap仍然作为HashSet的包装器实现,这会减少代码大小但不会减小堆大小,当然也不会增加效率。

因此,在所有这三种变体中,单个元素集实例由HashMap实例,Map.Entry实例,数组(条目)实例和HashSet实现实例组成。这个重量在所有三种变体中都有。

在“asList”变体的Arrays.asList构造过程中,会创建一个varargs参数的数组,Stream.of会在其周围创建一个包装器。

相比之下,“collectStream”变体可以依赖于优化的单个参数Spliterator变体,但它仍然会创建一个包含项目的流构建器(充当Stream)和a HashSet实施实例在其上运行。

同样,“asList”和“collectStream”都将在内部处理条件,因为无论其形式如何,都会对源进行迭代,以将所有元素添加到Collections.singleton,而在“基线”中“变体,只有一个元素添加的知识已被直接编码。

相比之下,Set.ofSet都创建了一个Set实现实例,只包含一个引用。这是最有效的形式,并且不需要运行时评估来决定必须使用此表单。

在此示例中,即使散列也必须被视为负面性能方面。虽然散列在大量元素的情况下具有很好的扩展性,但对于单个元素的情况来说,这是一个过时的操作,而不是专门的单元素ArrayList<Data> items=new ArrayList<>(); SpinnerDialog spinnerDialog; ImageView imageView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drawer_menu); // Searching With Spinner[START] initItems(); spinnerDialog =new SpinnerDialog(DrawerMenu.this,items,"Select Item"); spinnerDialog.bindOnSpinerListener(new OnSpinerItemClick() { @Override public void onClick(Data item, int position) { Toast.makeText(DrawerMenu.this,"Selected: "+item.getId()+" "+item.getTitle(),Toast.LENGTH_SHORT).show(); } }); imageView=(ImageView)findViewById(R.id.search_Quali); imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { spinnerDialog.showSpinerDialog(); } }); } private void initItems() { for(int i=0;i<100;i++) { items.add(new Data(101+i+"","Item"+(i+1))); } } class Data { String Id,title; Data(String id,String title) { this.title=title; this.Id=id; } public String getId() { return Id; } public String getTitle() { return title; } // Searching With Spinner[END] } 实现。

答案 2 :(得分:1)

ListSetMap的收藏家最终会创建ListSetMap个实例。
例如,这里是Collectors.toSet()正文:

public static <T>
Collector<T, ?, Set<T>> toSet() {
    return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add,
                               (left, right) -> { left.addAll(right); return left; },
                               CH_UNORDERED_ID);
}

所以你永远不会忘记new运算符声明。

Stream.of()允许操纵Stream 我认为这是使用它的唯一正当理由。