HashMap 的迭代方式一般有如下几种:
- 使用
for-each
循环遍历keySet()
- 使用
for-each
循环遍历entrySet()
- 使用
Iterator
遍历keySet()
- 使用
Iterator
遍历entrySet()
- 使用
Lambda
表达式 - 使用
Streams API
单线程 - 使用
Streams API
多线程
详情
基础数据
private Map<Integer,String> hashMap = new HashMap<>(){{
put(1,"死磕");
put(2,"死磕 Java");
put(3,"死磕 Spring");
put(4,"死磕 Redis");
put(5,"死磕 Java NIO");
put(6,"死磕 Netty");
put(7,"死磕 Java 新特性");
put(8,"死磕 Java 基础");
}};
使用 for-each 循环遍历 keySet()
@Test
public void test1() {
for (Integer key : hashMap.keySet()) {
System.out.println(key + "---" + hashMap.get(key));
}
}
执行结果:
1---死磕
2---死磕 Java
3---死磕 Spring
4---死磕 Redis
5---死磕 Java NIO
6---死磕 Netty
7---死磕 Java 新特性
8---死磕 Java 基础
使用 for-each 循环遍历 entrySet()
@Test
public void test2() {
for (Map.Entry<Integer,String> entry : hashMap.entrySet()) {
System.out.println(entry.getKey() + "---" + entry.getValue());
}
}
执行结果:
1---死磕
2---死磕 Java
3---死磕 Spring
4---死磕 Redis
5---死磕 Java NIO
6---死磕 Netty
7---死磕 Java 新特性
8---死磕 Java 基础
使用 Iterator 遍历 keySet()
@Test
public void test4() {
Iterator<Integer> iterator = hashMap.keySet().iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
System.out.println(key + "---" + hashMap.get(key));
}
}
执行结果:
1---死磕
2---死磕 Java
3---死磕 Spring
4---死磕 Redis
5---死磕 Java NIO
6---死磕 Netty
7---死磕 Java 新特性
8---死磕 Java 基础
使用 Iterator 遍历 entrySet()
@Test
public void test3() {
Iterator<Map.Entry<Integer,String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer,String> entry = iterator.next();
System.out.println(entry.getKey() + "---" + entry.getValue());
}
}
执行结果:
1---死磕
2---死磕 Java
3---死磕 Spring
4---死磕 Redis
5---死磕 Java NIO
6---死磕 Netty
7---死磕 Java 新特性
8---死磕 Java 基础
使用 Lambda 表达式
@Test
public void test5() {
hashMap.forEach((key,value) -> {
System.out.println(key + "---" + hashMap.get(key));
});
}
执行结果:
1---死磕
2---死磕 Java
3---死磕 Spring
4---死磕 Redis
5---死磕 Java NIO
6---死磕 Netty
7---死磕 Java 新特性
8---死磕 Java 基础
使用 Streams API 单线程
@Test
public void test6() {
hashMap.entrySet().stream().forEach((entry) -> {
System.out.println(entry.getKey() + "---" + entry.getValue());
});
}
执行结果:
1---死磕
2---死磕 Java
3---死磕 Spring
4---死磕 Redis
5---死磕 Java NIO
6---死磕 Netty
7---死磕 Java 新特性
8---死磕 Java 基础
使用 Streams API 多线程
@Test
public void test7() {
hashMap.entrySet().parallelStream().forEach((entry) -> {
System.out.println(entry.getKey() + "---" + entry.getValue());
});
}
执行结果:
@Test
public void test7() {
hashMap.entrySet().parallelStream().forEach((entry) -> {
System.out.println(entry.getKey() + "---" + entry.getValue());
});
}
扩展
性能测试
下面我们来测试这七种方法的性能,使用性能测试专用工具 JMH(不要再用 System.currentTimeMillis()
了)。
先添加依赖:
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.23</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.23</version>
<scope>provided</scope>
</dependency>
再写测试代码:
@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 1s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class HashMapTest {
static Map<Integer,String> hashMap = new HashMap<>(){{
for (int i = 0 ; i < 10000 ; i++) {
put(i,"sike-" + i);
}
}};
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(HashMapTest.class.getSimpleName())
.output("/Users/chenssy/Downloads/jmh-hashMap.log")
.build();
new Runner(opt).run(); // 执行测试
}
@Benchmark
public void keySet() {
for (Integer key : hashMap.keySet()) {
Integer key1 = key;
String value1 = hashMap.get(key1);
}
}
@Benchmark
public void entrySet() {
for (Map.Entry<Integer,String> entry : hashMap.entrySet()) {
Integer key = entry.getKey();
String value1 = entry.getValue();
}
}
@Benchmark
public void entrySetIterator() {
Iterator<Map.Entry<Integer,String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer,String> entry = iterator.next();
Integer key = entry.getKey();
String value = entry.getValue();
}
}
@Benchmark
public void keySetIterator() {
Iterator<Integer> iterator = hashMap.keySet().iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
String value = hashMap.get(key);
}
}
@Benchmark
public void lambda() {
hashMap.forEach((key,value) -> {
Integer key1 = key;
String value1 = value;
});
}
@Benchmark
public void stream() {
hashMap.entrySet().stream().forEach((entry) -> {
Integer key = entry.getKey();
String value = entry.getValue();
});
}
}
我们分别验证 100、1000、10000 个元素的测试结果:
- 100
Benchmark Mode Cnt Score Error Units
HashMapTest.entrySet avgt 5 362.568 ± 8.211 ns/op
HashMapTest.entrySetIterator avgt 5 361.015 ± 5.712 ns/op
HashMapTest.keySet avgt 5 749.805 ± 2.336 ns/op
HashMapTest.keySetIterator avgt 5 752.219 ± 3.765 ns/op
HashMapTest.lambda avgt 5 265.254 ± 1.614 ns/op
HashMapTest.stream avgt 5 375.628 ± 2.425 ns/op
- 1000
Benchmark Mode Cnt Score Error Units
HashMapTest.entrySet avgt 5 3211.032 ± 139.991 ns/op
HashMapTest.entrySetIterator avgt 5 3323.444 ± 17.326 ns/op
HashMapTest.keySet avgt 5 7164.590 ± 109.561 ns/op
HashMapTest.keySetIterator avgt 5 7204.525 ± 100.791 ns/op
HashMapTest.lambda avgt 5 2458.673 ± 81.381 ns/op
HashMapTest.stream avgt 5 3992.651 ± 41.205 ns/op
- 10000
Benchmark Mode Cnt Score Error Units
HashMapTest.entrySet avgt 5 35797.452 ± 5896.667 ns/op
HashMapTest.entrySetIterator avgt 5 35611.409 ± 6153.680 ns/op
HashMapTest.keySet avgt 5 57320.843 ± 42999.041 ns/op
HashMapTest.keySetIterator avgt 5 57748.329 ± 40455.889 ns/op
HashMapTest.lambda avgt 5 28654.233 ± 598.318 ns/op
HashMapTest.stream avgt 5 37840.969 ± 3215.428 ns/op
从测试结果上面来看 entrySet
和 Lambda
表达式两个性能最佳。keySet
的性能最差,主要原因是 keySet
获取 value 的时候还需要调用 HashMap 的 get()
查询。
每个人会因为使用机器、环境、JVM 版本的不同而导致测试的结果不同。
最后
- 对于大多数场景,使用
entrySet()
遍历是最最佳的选择。 - 如果追求代码简洁,推荐使用 Lambda 表达式。
- 如果需要在迭代时修改 HashMap ,推荐使用迭代器
entrySet()
的方式。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。