ArrayList到HashSet的一次优化
一、前言
最近同事分享了一次遍历List
判断元素是否存在的优化技巧,亲自实验的一波,还是很nb。
(一)本地实验结果
1. 环境信息
- jdk1.8
- 测试工具:JMH 1.22
- 测试机器:mbp 16C32G
2. 测试结果
在同样10000个元素数量的情况下进行包含判断,HashSet
比ArrayList
快了约7660多倍。虽然实验之前知道HashSet
肯定比ArrayList
性能快,但是结果还是有点超出预期。
* 测试结果(按照吞吐量来测试):
* Benchmark Mode Cnt Score Error Units
* perfTuning.TestListAndHashSet.checkArrayListWithContains thrpt 5 24.929 ± 9.703 ops/ms
* perfTuning.TestListAndHashSet.checkArrayListWithIndex thrpt 5 25.505 ± 1.811 ops/ms
* perfTuning.TestListAndHashSet.checkArrayListWithIterator thrpt 5 23.496 ± 3.172 ops/ms
* perfTuning.TestListAndHashSet.checkHashSet thrpt 5 191505.153 ± 9573.444 ops/ms
二、测试代码
(一)测试源码
- 这边考虑了极端情况,待判断的元素默认放在了最后。
/**
* @Author: allen
* @Date: 2022/2/16 5:45 下午
* @Description: 对比ArrayList和HashSet各种场景下判断元素是否存在的性能情况
*
* 测试结果:
* Benchmark Mode Cnt Score Error Units
* perfTuning.TestListAndHashSet.checkArrayListWithContains thrpt 5 24.929 ± 9.703 ops/ms
* perfTuning.TestListAndHashSet.checkArrayListWithIndex thrpt 5 25.505 ± 1.811 ops/ms
* perfTuning.TestListAndHashSet.checkArrayListWithIterator thrpt 5 23.496 ± 3.172 ops/ms
* perfTuning.TestListAndHashSet.checkHashSet thrpt 5 191505.153 ± 9573.444 ops/ms
*/
@State(Scope.Benchmark)
public class TestListAndHashSet {
public static final HashSet<String> hashSet = new HashSet<String>();
public static final ArrayList<String> arrayList = new ArrayList<String>();
/**
* 构建测试集合HashSet和ArrayList,都存放10001个元素,并将待判断元素添加至尾部
*/
@Setup(Level.Trial)
public void init() {
for (int i = 0; i < 10000; i++) {
hashSet.add(UuidUtil.getStrUuid());
}
hashSet.add("hashSet-test");
for (int i = 0; i < 10000; i++) {
arrayList.add(UuidUtil.getStrUuid());
}
arrayList.add("arrayList-test");
}
/**
* HashSet通过定位key的hash值进行查找,时间复杂度O(1)
* @return Boolean
*/
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public Boolean checkHashSet() {
return hashSet.contains("hashSet-test");
}
/**
* 通过迭代器遍历ArrayList进行逐个比对,时间复杂度O(n)
* 可以查看编译之后的字节码,会使用迭代器进行遍历操作
* @return Boolean
*/
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public Boolean checkArrayListWithIterator() {
for (String s : arrayList) {
if (s.equals("arrayList-test")) {
return true;
}
}
return false;
}
/**
* 通过index手工遍历ArrayList进行逐个比对,时间复杂度O(n)
* @return Boolean
*/
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public Boolean checkArrayListWithIndex() {
for (int i = 0; i < arrayList.size(); i++) {
if (arrayList.get(i).equals("arrayList-test")) {
return true;
}
}
return false;
}
/**
* ArrayList的contains方法通过遍历list进行逐个比对,时间复杂度O(n)
* @return Boolean
*/
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public Boolean checkArrayListWithContains() {
return arrayList.contains("arrayList-test");
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(TestListAndHashSet.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
(二)简单记录下心得
- 对于
ArrayList
这种底层使用数组实现的数据结构,使用数组index遍历会比使用迭代器遍历效率高一些,迭代器遍历更适合链表类数据; ArrayList
的contains
方法,源码中默认使用的就是index遍历,因此contains
效率和我手动index遍历的效率差不多;HashSet
的contains
方法,使用的是hash
值定位,同HashMap
的hash
定位,时间复杂度是O(1),效率远大于遍历操作;
之后存在集合包含判断的场景,HashSet
就是大妙招。
《Java性能优化之影响性能的那些细节》持续更新中...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。