我现在已经从提供的优秀答案中标记了大多数建议的选项。像大多数与性能相关的非平凡问题一样,关于哪个最好的答案是“视情况而定”。
我所有的测试都是用JMH Java Microbenchmarking Harness执行的。
这些测试中的大多数都是使用JDK 1.8执行的,尽管我也使用JDK 1.7执行了一些测试,以确保其性能不会有太大差异(几乎相同)。我测试了从到目前为止提供的答案中获取的以下技术:
1. Java 8 Stream - 如果使用 Java8,则使用 I 的解决方案是一种可能性:stream()
public List<Integer> testJava8Stream(List<Integer> listInts) {
return listInts.stream().distinct().collect(Collectors.toList());
}
优点现代Java 8方法,没有第三方依赖关系
缺点 需要 Java 8
2. 添加到列表 - Victor2748 提出的解决方案,其中构建并添加新列表,当且仅当列表尚未包含该值时。请注意,我还以原始大小(最大可能)预先分配目标列表,以防止任何重新分配:
public List<Integer> testAddingToList(List<Integer> listInts) {
List<Integer> listDistinctInts = new ArrayList<>(listInts.size());
for(Integer i : listInts)
{
if( !listDistinctInts.contains(i) ) { listDistinctInts.add(i); }
}
return listDistinctInts;
}
pros 适用于任何Java版本,无需创建集合然后复制,无需第三方设备
缺点 在构建列表时需要反复检查列表中的现有值
3. GS Collections Fast(现为 Eclipse Collections) - Craig P. Motlin 使用 GS Collections 库及其自定义列表类型提出的解决方案:FastList
public List<Integer> testGsCollectionsFast(FastList listFast)
{
return listFast.distinct();
}
优点 据报道,非常快速,简单的表达性代码,可在Java 7和8中工作
缺点 需要第三方库和快速列表
,而不是常规列表<Integer>
4. GS Collections Adapted - FastList解决方案并没有完全比较同类产品,因为它需要传递给方法而不是一个好的方法,所以我也测试了Craig提出的适配器方法:FastList
ArrayList<Integer>
public List<Integer> testGsCollectionsAdapted(List<Integer> listInts)
{
return listAdapter.adapt(listInts).distinct();
}
pros 不需要 FastList
,可在 Java 7 和 8 中工作
缺点必须适应列表,所以可能表现不佳,需要第三方库
5. 番石榴不可变集 - 路易斯·瓦瑟曼在评论中提出的方法,卢声远圣远卢在回答中使用番石榴提出的方法:
public List<Integer> testGuavaImmutable(List<Integer> listInts)
{
return ImmutableSet.copyOf(listInts).asList();
}
优点 据说非常快,可以在Java 7或8中工作
缺点 返回不可变列表
,无法处理输入列表中的 null,并且需要第三方库
7. HashSet - 我最初的想法(也由EverV0id,ulix和Radiodef推荐)
public List<Integer> testHashSet(List<Integer> listInts)
{
return new ArrayList<Integer>(new HashSet<Integer>(listInts));
}
pros 适用于 Java 7 和 8,没有第三方依赖项
缺点 不保留列表的原始顺序,必须构造集合然后复制到列表。
6. LinkedHashSet - 由于解决方案没有保留原始列表中整数的顺序,我还测试了一个使用LinkedHashSet来保持顺序的版本:HashSet
public List<Integer> testLinkedHashSet(List<Integer> listInts)
{
return new ArrayList<Integer>(new LinkedHashSet<Integer>(listInts));
}
pros 保留原始排序,适用于 Java 7 和 8,无第三方依赖项
缺点 不太可能像常规哈希集
方法一样快
结果
以下是我对各种不同大小的结果(结果按从最慢到最快的顺序排列):listInts
1. 取自 ArrayList 中 100,000 个介于 0-50,000 之间的随机 ints(即大列表,一些重复项)
Benchmark Mode Samples Mean Mean error Units
AddingToList thrpt 10 0.505 0.012 ops/s
Java8Stream thrpt 10 234.932 31.959 ops/s
LinkedHashSet thrpt 10 262.185 16.679 ops/s
HashSet thrpt 10 264.295 24.154 ops/s
GsCollectionsAdapted thrpt 10 357.998 18.468 ops/s
GsCollectionsFast thrpt 10 363.443 40.089 ops/s
GuavaImmutable thrpt 10 469.423 26.056 ops/s
2. 取自 ArrayList 中 0-50 之间的 1000 个随机 ints(即中等列表,许多重复项)
Benchmark Mode Samples Mean Mean error Units
AddingToList thrpt 10 32794.698 1154.113 ops/s
HashSet thrpt 10 61622.073 2752.557 ops/s
LinkedHashSet thrpt 10 67155.865 1690.119 ops/s
Java8Stream thrpt 10 87440.902 13517.925 ops/s
GsCollectionsFast thrpt 10 103490.738 35302.201 ops/s
GsCollectionsAdapted thrpt 10 143135.973 4733.601 ops/s
GuavaImmutable thrpt 10 186301.330 13421.850 ops/s
3. 取自 ArrayList 中 0-100 之间的 100 个随机 ints(即小列表,一些重复项)
Benchmark Mode Samples Mean Mean error Units
AddingToList thrpt 10 278435.085 14229.285 ops/s
Java8Stream thrpt 10 397664.052 24282.858 ops/s
LinkedHashSet thrpt 10 462701.618 20098.435 ops/s
GsCollectionsAdapted thrpt 10 477097.125 15212.580 ops/s
GsCollectionsFast thrpt 10 511248.923 48155.211 ops/s
HashSet thrpt 10 512003.713 25886.696 ops/s
GuavaImmutable thrpt 10 1082006.560 18716.012 ops/s
4. 取自 ArrayList 中 0-50 之间的 10 个随机 ints(即小列表,几个重复项)
Benchmark Mode Samples Mean Mean error Units
Java8Stream thrpt 10 2739774.758 306124.297 ops/s
LinkedHashSet thrpt 10 3607479.332 150331.918 ops/s
HashSet thrpt 10 4238393.657 185624.358 ops/s
GsCollectionsAdapted thrpt 10 5919254.755 495444.800 ops/s
GsCollectionsFast thrpt 10 7916079.963 1708778.450 ops/s
AddingToList thrpt 10 7931479.667 966331.036 ops/s
GuavaImmutable thrpt 10 9021621.880 845936.861 ops/s
结论
如果您只从列表中获取一次不同的项目,并且列表不是很长,那么这些方法中的任何一个都应该足够了。
最有效的通用方法来自第三方图书馆:GS Collections和Guava的表现令人钦佩。
在选择性能最高的方法时,您可能需要考虑列表的大小和可能的重复项数。
只有当值尚未在其中时,才将其添加到新列表的幼稚方法对于小列表非常有用,但是一旦输入列表中的值超过少数,它就会执行最坏的尝试方法。
在大多数情况下,番石榴方法的效果最快。但请注意这些限制:返回的列表是,输入列表不能包含 null。ImmutableSet.copyOf(listInts).asList()
Immutable
该方法执行非第三方方法中最好的方法,通常比Java 8流更好,但对整数进行重新排序(根据您的用例,这可能是也可能不是问题)。HashSet
该方法保持排序,但不足为奇通常比HashSet方法差。LinkedHashSet
当使用具有复杂 HashCode 计算的数据类型列表时,和 方法的性能都会更差,因此,如果您尝试从 中选择不同的 ,请进行自己的分析。HashSet
LinkedHashSet
Foo
List<Foo>
如果您已经将GS Collections作为依赖项,那么它的性能非常好,并且比ImmutableList Guava方法更灵活。如果没有将其作为依赖项,则如果选择不同项的性能对应用程序的性能至关重要,则值得考虑添加它。
令人失望的是,Java 8流似乎表现相当差。可能有比我使用的方式更好的方法来编写调用代码,因此当然欢迎评论或其他答案。distinct()
铌。我不是MicroBenchmarking的专家,所以如果有人发现我的结果或方法有缺陷,请通知我,我会努力纠正答案。