【Java必修课】ArrayList与HashSet的contains方法性能比较(JMH性能测试)
1 简介
在日常开发中,ArrayList和HashSet都是Java中很常用的集合类。
ArrayList是List接口最常用的实现类;HashSet则是保存唯一元素Set的实现。
本文主要对两者共有的方法contains()做一个简单的讨论,主要是性能上的对比,并用JMH(ava Microbenchmark Harness)进行测试比较。
2 先看JMH测试结果
我们使用一个由OpenJDK/Oracle里面开发了Java编译器的大牛们所开发的Micro Benchmark Framework来测试。下面简单展示一下使用过程。
2.1 Maven导入相关依赖
导入JMH的相关依赖,可以去官网查看最新版本:
<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${openjdk.jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${openjdk.jmh.version}</version>
</dependency>
</dependencies>
<properties>
<openjdk.jmh.version>1.19</openjdk.jmh.version>
</properties>
2.2 创建测试相关的类
2.2.1 集合储存对象的类
因为要测试集合类的方法,所以我们创建一个类来表示集合所储存的对象。如下:
@Data
@AllArgsConstructor(staticName = "of")
public class Student {
private Long id;
private String name;
}
2.2.2 JMH测试类
接下来我们就来写测试性能对比的类,代码如下:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class ContainsPerformanceTest {
@State(Scope.Thread)
public static class MyState {
private Set<Student> studentSet = new HashSet<>();
private List<Student> studentList = new ArrayList<>();
private Student targetStudent = Student.of(99L, "Larry");
@Setup(Level.Trial)
public void prepare() {
long MAX_COUNT = 10000;
for (long i = 0; i < MAX_COUNT; i++) {
studentSet.add(Student.of(i, "MQ"));
studentList.add(Student.of(i, "MQ"));
}
studentList.add(targetStudent);
studentSet.add(targetStudent);
}
}
@Benchmark
public boolean arrayList(MyState state) {
return state.studentList.contains(state.targetStudent);
}
@Benchmark
public boolean hashSet(MyState state) {
return state.studentSet.contains(state.targetStudent);
}
public static void main(String[] args) throws Exception {
Options options = new OptionsBuilder()
.include(ContainsPerformanceTest.class.getSimpleName())
.threads(6)
.forks(1)
.warmupIterations(3)
.measurementIterations(6)
.shouldFailOnError(true)
.shouldDoGC(true)
.build();
new Runner(options).run();
}
}
测试类注解说明:
- @BenchmarkMode:表示进行Benchmark时使用的模式;
AverageTime表示测试调用的平均时间。 - @OutputTimeUnit:测试的度量时间单位;
NANOSECONDS表示使用纳秒为单位。 - @State:接受一个
Scope参数表示状态的共享范围;Scope.Thread表示每个线程独享。 - @Setup:执行Benchmark前执行,类似于
JUnit的@BeforeAll。 - @Benchmark:进行Benchmark的对象,类似于
JUnit的@Test。
测试类启动参数Options说明:
- include:benchmark所在的类名;
- threads:每个进程中的测试线程数;
- fork:进程数,如果为3,则JMH会fork出3个进程来测试;
- warmupIterations:预热的迭代次数,
- measurementIterations:实际测量的迭代次数。
2.3 测试结果
设置好参数后,就可以跑测试了。测试结果如下:
# Benchmark: ContainsPerformanceTest.arrayList
# Run progress: 0.00% complete, ETA 00:00:18
# Fork: 1 of 1
# Warmup Iteration 1: 42530.408 ±(99.9%) 2723.999 ns/op
# Warmup Iteration 2: 17841.988 ±(99.9%) 1882.026 ns/op
# Warmup Iteration 3: 18561.513 ±(99.9%) 2021.506 ns/op
Iteration 1: 18499.568 ±(99.9%) 2126.172 ns/op
Iteration 2: 18975.407 ±(99.9%) 2004.509 ns/op
Iteration 3: 19386.851 ±(99.9%) 2248.536 ns/op
Iteration 4: 19279.722 ±(99.9%) 2102.846 ns/op
Iteration 5: 19796.495 ±(99.9%) 1974.987 ns/op
Iteration 6: 21363.962 ±(99.9%) 2175.961 ns/op
Result "ContainsPerformanceTest.arrayList":
19550.334 ±(99.9%) 2771.595 ns/op [Average]
(min, avg, max) = (18499.568, 19550.334, 21363.962), stdev = 988.377
CI (99.9%): [16778.739, 22321.929] (assumes normal distribution)
# Benchmark: ContainsPerformanceTest.hashSet
# Run progress: 50.00% complete, ETA 00:00:16
# Fork: 1 of 1
# Warmup Iteration 1: 10.662 ±(99.9%) 0.209 ns/op
# Warmup Iteration 2: 11.177 ±(99.9%) 1.077 ns/op
# Warmup Iteration 3: 9.467 ±(99.9%) 1.462 ns/op
Iteration 1: 9.540 ±(99.9%) 0.535 ns/op
Iteration 2: 9.388 ±(99.9%) 0.365 ns/op
Iteration 3: 10.604 ±(99.9%) 1.008 ns/op
Iteration 4: 9.361 ±(99.9%) 0.154 ns/op
Iteration 5: 9.366 ±(99.9%) 0.458 ns/op
Iteration 6: 9.274 ±(99.9%) 0.237 ns/op
Result "ContainsPerformanceTest.hashSet":
9.589 ±(99.9%) 1.415 ns/op [Average]
(min, avg, max) = (9.274, 9.589, 10.604), stdev = 0.505
CI (99.9%): [8.174, 11.004] (assumes normal distribution)
# Run complete. Total time: 00:00:32
Benchmark Mode Cnt Score Error Units
ContainsPerformanceTest.arrayList avgt 6 19550.334 ± 2771.595 ns/op
ContainsPerformanceTest.hashSet avgt 6 9.589 ± 1.415 ns/op
经过测试,发现两者耗时差异极大,ArrayList大概是20K纳秒,而HashSet则10纳秒左右。两者完全不在一个数量级上。
3 源码分析
通过测试得知两者差异极大,就小窥一下源码分析分析。
3.1 ArrayList的contains()
ArrayList的底层使用数组作为数据存储,当给定一个Object去判断是否存在,需要去遍历数组,与每个元素对比。
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
从源码可以发现,contains()方法是通过调用indexOf()来判断的,而后者就是需要遍历数组,直到找到那个与入参相等的元素才会停止。因为,ArrayList的contains()方法的时间复杂度为O(n),也就是说,时间取决于长度,而且是正比的关系。
3.2 HashSet的contains()
HashSet底层是通过HashMap来实现的,而HashMap的底层结构为数组+链表,JDK 8后改为数组+链表+红黑树。
HashMap的相关代码如下:
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
首先通过获取Hash值来找,如果Hash值相等且对象也相等,则找到。一般来说,在hashCode()方法实现没问题的情况下,发生Hash冲突的情况是比较少。所以可以认为,大部分情况下,contains()的时间复杂度为O(1),元素个数不影响其速度。如果发生Hash冲突,在链表长度小于8时,时间复杂度为O(n);在链表大于8时,转化为红黑树,时间复杂度为O(logn)。
一般地,我们认为,HashSet/HashMap的查找的时间复杂度为O(1)。
4 总结
通过JMH测试我们发现ArrayList和HashSet的contains()方法性能差异很大。经过源码分析得知,ArrayList对应的时间复杂度为O(n),而HashSet的时间度为O(1)。
欢迎关注公众号<南瓜慢说>,将持续为你更新...
【Java必修课】ArrayList与HashSet的contains方法性能比较(JMH性能测试)的更多相关文章
- ArrayList和HashSet的Contains()方法(转)
来源: ArrayList和HashSet的Contains()方法 笔试题: package com.champion.test.exam; import java.util.ArrayList; ...
- ajax与servlet交互(通过JSON),JAVA的arraylist传到前端的方法
所实现的效果:首先从前端(ajax)传参数给servlet,然后servlet经过处理,把arraylist类型的参数以JSON字符串的形式返回给前端(ajax),然后前端经过解析,把JSON字符串解 ...
- org.hibernate.MappingException:Unknown entity:java.util.ArrayList
1.错误描述 [CQ] ERROR [http-apr-8888-exec-3] com.opensymphony.xwork2.util.logging.commons.CommonsLogger. ...
- Java中如何克隆集合——ArrayList和HashSet深拷贝
编程人员经常误用各个集合类提供的拷贝构造函数作为克隆List,Set,ArrayList,HashSet或者其他集合实现的方法.需要记住的是,Java集合的拷贝构造函数只提供浅拷贝而不是深拷贝,这意味 ...
- 【转】Java如何克隆集合——深度拷贝ArrayList和HashSet
原文网址:http://blog.csdn.net/cool_sti/article/details/21658521 原英文链接:http://javarevisited.blogspot.hk/2 ...
- Java中ArrayList,Vector,LinkedList,HashMap,HashTable,HashSet对比及总结
1.所有的集合的父类都是Collection的接口 2.Set List Map 区别 A 在Set里面:无法添加元素的顺序,所以Set里面的元素不能重复 B 在List中:有索引号,类似于数组, ...
- Junit 注解 类加载器 .动态代理 jdbc 连接池 DButils 事务 Arraylist Linklist hashset 异常 哈希表的数据结构,存储过程 Map Object String Stringbufere File类 文件过滤器_原理分析 flush方法和close方法 序列号冲突问题
Junit 注解 3).其它注意事项: 1).@Test运行的方法,不能有形参: 2).@Test运行的方法,不能有返回值: 3).@Test运行的方法,不能是静态方法: 4).在一个类中,可以同时定 ...
- 浅谈Java语言中ArrayList和HashSet的区别
Java语言中ArrayList和HashSet的区别 2019-04-10 13:22:49 一.基本区别 首先一起看个实例,其代码如下: package com.MrZ_baby.com; i ...
- Java基础——ArrayList方法全解(字典版)
引言 在使用集合 ArrayList 的时候,经常使用add.remove等,其他的没用过,甚至没听说过的还有很多.现在在这个教程中,简单的了解一下,不要求全都记下.相当于在你脑袋里建一个索引,就是有 ...
随机推荐
- Python 开发植物大战僵尸游戏
作者:楷楷 链接:https://segmentfault.com/a/1190000019418065 开发思路 完整项目地址: https://github.com/371854496/pygam ...
- 小白专场-是否同一颗二叉搜索树-python语言实现
目录 一.二叉搜索树的相同判断 二.问题引入 三.举例分析 四.方法探讨 4.1 中序遍历 4.2 层序遍历 4.3 先序遍历 4.4 后序遍历 五.总结 六.代码实现 一.二叉搜索树的相同判断 二叉 ...
- 自定义View入门-绘制基础(1)
### 前言 说道自定义View,我们一定会想到,自定义View的绘制流程 - 测量阶段(measure) - 布局阶段(layout) - 绘制阶段(draw) 我们看到的一些炫酷的view效果,都 ...
- Vue学习之todolist删除功能
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- [C++] 头文件中的#ifndef,#define,#endif以及#pragma用法
想必很多人都看过“头文件中用到的 #ifndef/#define/#endif 来防止该头文件被重复引用”.但是是否能理解“被重复引用”是什么意思?头文件被重复引用了,会产生什么后果?是不是所有的头文 ...
- Centos6安装MySQL5.7(yum方式)
1. 下载并安装用来配置mysql的yum源的rpm包 # 下载 wget http://repo.mysql.com/mysql57-community-release-el6-10.noarch. ...
- 【求赐教】VMware workstation 转VSphere
首先我从其他电脑拷贝过来一台虚拟机(这个说法不知道准不准确,就是把所有文件夹都拷贝过来了),然后打开VMware,通过"打开虚拟机"这个操作,直接找到本地的.vmx文件,如下图所示 ...
- Spring Boot 整合 Web 开发
这一节我们主要学习如何整合 Web 相关技术: Servlet Filter Listener 访问静态资源 文件上传 文件下载 Web三大基本组件分别是:Servlet,Listener,Filte ...
- GetThreadTimes获取其它线程cpu时间
http://www.cnblogs.com/eaglet/archive/2009/03/11/1408809.html 鄙视下上面的垃圾博文,纯粹忽悠人 参考文章: http://blog.kal ...
- Postgressql高可用(pgpool+异步流复制)
文档结构: 由于博主之前是Oracle dba,oracle 高可用一般是rac,以及搭建ADG,一个是基于实例的高可用,一个是基于数据的容灾,甚至也有rac+adg的方式.Postgres有同步和异 ...