Day10-Java中HashMap几种遍历方式与性能分析

简介: 笔记

先上结论:

  • Java中HashMap有四种遍历方式,七种遍历方法
  • 使用迭代器(Iterator)EntrySet 的方式进行遍历性能最高,最安全


(1)HashMap遍历


接下来我们来看每种遍历方式的具体实现代码。

package com.tset.three;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
 * @author :caizhengjie
 * @description:TODO
 * @date :2021/7/24 11:00 下午
 */
public class TestMap {
    public static void main(String[] args) {
        // 创建map集合
        Map<Integer, String> map = new HashMap<Integer, String>();
        map.put(1, "Alex");
        map.put(2, "Lucy");
        map.put(3, "Pony");
        map.put(4, "Jerry");
        map.put(5, "Jack");
        /**
         * 循环遍历map集合的几种方式(四种遍历方式,七种遍历方法)
         * 可以分为四大类:
         * 1.迭代器(Iterator)方式遍历
         * 2.For Each 方式遍历
         * 3.Lambda 表达式遍历(JDK 1.8+)
         * 4.Streams API 遍历(JDK 1.8+)
         *
         * 但每种类型下又有不同的实现方式,因此具体的遍历方式又可以分为以下 7 种:
         * 1.使用迭代器(Iterator)EntrySet 的方式进行遍历;
         * 2.使用迭代器(Iterator)KeySet 的方式进行遍历;
         * 3.使用 For Each EntrySet 的方式进行遍历;
         * 4.使用 For Each KeySet 的方式进行遍历;
         * 5.使用 Lambda 表达式的方式进行遍历;
         * 6.使用 Streams API 单线程的方式进行遍历;
         * 7.使用 Streams API 多线程的方式进行遍历;
         */
        /**
         * 1.使用迭代器(Iterator)EntrySet 的方式进行遍历;
         */
        Iterator<Map.Entry<Integer,String>> iterator1 = map.entrySet().iterator();
        while (iterator1.hasNext()){
            Map.Entry<Integer,String> entry = iterator1.next();
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }
        /**
         * 2.使用迭代器(Iterator)KeySet 的方式进行遍历;
         */
        Iterator<Integer> iterator2 = map.keySet().iterator();
        while (iterator2.hasNext()){
            Integer key = iterator2.next();
            System.out.println(key + " : " + map.get(key));
        }
        /**
         * 3.使用 For Each EntrySet 的方式进行遍历;
         */
        for (Map.Entry<Integer,String> entry : map.entrySet()){
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }
        /**
         * 4.使用 For Each KeySet 的方式进行遍历;
         */
        for (Integer key : map.keySet()){
            System.out.println(key + " : " + map.get(key));
        }
        /**
         * 5.使用 Lambda 表达式的方式进行遍历;
         */
        map.forEach((key,value) -> {
            System.out.println(key + " : " + value);
        });
        /**
         * 6.使用 Streams API 单线程的方式进行遍历;
         */
        map.entrySet().stream().forEach((entry) -> {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        });
        System.out.println("ss");
        /**
         * 7.使用 Streams API 多线程的方式进行遍历;
         */
        map.entrySet().parallelStream().forEach((entry) -> {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        });
    }
}

运行结果:

1 : Alex
2 : Lucy
3 : Pony
4 : Jerry
5 : Jack


(2)性能分析


前面的结论说EntrySet的方式性能最高,EntrySet之所以比KeySet的性能高是因为,KeySet在循环时使用了map.get(key),而map.get(key)相当于又遍历了一遍 Map 集合去查询key所对应的值。为什么要用“又”这个词?那是因为在使用迭代器或者 for 循环时,其实已经遍历了一遍 Map 集合了,因此再使用map.get(key)查询时,相当于遍历了两遍。


而EntrySet只遍历了一遍 Map 集合,之后通过代码“Entry<Integer, String> entry = iterator.next()”把对象的key和value值都放入到了Entry对象中,因此再获取key和value值时就无需再遍历 Map 集合,只需要从Entry对象中取值就可以了。


所以,EntrySet的性能比KeySet的性能高出了一倍,因为KeySet相当于循环了两遍 Map 集合,而EntrySet只循环了一遍。


(3)安全性分析


上面我们了解到使用KeySet的遍历方式的性能最高,下面继续从安全性来分析一下哪种遍历方式更安全,以删除map集合中key为1的元素为例,分别测试以下四种方式删除map集合中的元素,分为安全删除与不安全删除。


使用迭代器的方式进行删除

使用For Each的方式进行删除

使用 Lambda 表达式的方式进行删除

使用 Stream 的方式进行删除


(3.1)迭代器的方式进行删除

/**
 * 1.使用迭代器的方式进行删除(安全删除)
 */
Iterator<Map.Entry<Integer,String>> iterator1 = map.entrySet().iterator();
while (iterator1.hasNext()){
    Map.Entry<Integer,String> entry = iterator1.next();
    if (entry.getKey() == 1){
        System.out.println("del " + " : " + entry.getKey() + " : " + entry.getValue());
        iterator1.remove();
    } else {
        System.out.println("show " + " : " + entry.getKey() + " : " + entry.getValue());
    }
}

运行结果:

del  : 1 : Alex
show  : 2 : Lucy
show  : 3 : Pony
show  : 4 : Jerry
show  : 5 : Jack

结论:迭代器中循环删除数据安全


(3.2)For Each的方式进行删除


/**
 * 2.使用For Each的方式进行删除(不安全)
 */
for (Map.Entry<Integer,String> entry : map.entrySet()){
    if (entry.getKey() == 1){
        System.out.println("del " + " : " + entry.getKey() + " : " + entry.getValue());
        map.remove(entry.getKey());
    } else {
        System.out.println("show " + " : " + entry.getKey() + " : " + entry.getValue());
    }
}

运行结果:可以看出抛出的是并发修改异常

del  : 1 : Alex
Exception in thread "main" java.util.ConcurrentModificationException
  at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
  at java.util.HashMap$EntryIterator.next(HashMap.java:1479)
  at java.util.HashMap$EntryIterator.next(HashMap.java:1477)
  at com.tset.three.TestMapDel.main(TestMapDel.java:44)

结论:For Each中循环删除数据不安全


(3.3)Lambda 表达式的方式进行删除

/**
 * 3.使用 Lambda 表达式的方式进行删除(不安全);
 */
map.forEach((key,value) -> {
    if (key == 1){
        System.out.println("del " + " : " + key + " : " + value);
        map.remove(key);
    } else {
        System.out.println("show " + " : " + key + " : " + value);
    }
});

运行结果:

del  : 1 : Alex
show  : 2 : Lucy
show  : 3 : Pony
show  : 4 : Jerry
show  : 5 : Jack
Exception in thread "main" java.util.ConcurrentModificationException
  at java.util.HashMap.forEach(HashMap.java:1292)
  at com.tset.three.TestMapDel.main(TestMapDel.java:56)

测试结果:Lambda 循环中删除数据非安全

但是,Lambda 删除也有删除元素的正确方式

/**
 * 3.使用 Lambda 表达式的方式进行删除(安全);
 */
map.keySet().removeIf(key -> key == 1);
map.forEach((key,value) -> {
    System.out.println(key + " : " + value);
});

运行结果:

2 : Lucy
3 : Pony
4 : Jerry
5 : Jack

从上面的代码可以看出,可以先使用Lambda 的removeIf 删除多余的数据,再进行循环是一种正确操作集合的方式。


(3.4)Stream 的方式进行删除

 /**
  * 4.使用 Stream 的方式进行删除(不安全);
  */
 map.entrySet().stream().forEach((entry) -> {
     if (entry.getKey() == 1){
         System.out.println("del " + " : " + entry.getKey() + " : " + entry.getValue());
         map.remove(entry.getKey());
     } else {
         System.out.println("show " + " : " + entry.getKey() + " : " + entry.getValue());
     }
 });

运行结果:

del  : 1 : Alex
show  : 2 : Lucy
show  : 3 : Pony
show  : 4 : Jerry
show  : 5 : Jack
Exception in thread "main" java.util.ConcurrentModificationException
  at java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.java:1704)
  at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
  at com.tset.three.TestMapDel.main(TestMapDel.java:77)

测试结果:Stream 循环中删除数据非安全。

Stream 循环的正确方式:

/**
 * 4.使用 Streams 的方式进行删除(安全);
 */
map.entrySet().stream().filter(m -> 1 != m.getKey()).forEach((entry) -> {
    System.out.println("show " + " : " + entry.getKey() + " : " + entry.getValue());
});

运行结果:

show  : 2 : Lucy
show  : 3 : Pony
show  : 4 : Jerry
show  : 5 : Jack

从上面的代码可以看出,可以使用Stream 中的filter 过滤掉无用的数据,再进行遍历也是一种安全的操作集合的方式。


(4)总结


安全删除map集合元素:


可以使用迭代器删除元素

可以使用Lambda 中removeIf方法删除元素

可以使用Stream中的filter 过滤掉无用的数据,再进行遍历

不安全删除map集合元素:


使用For Each循环删除元素会抛出并发修改异常

注意:不要在 foreach 循环里进行元素的 remove/add 操作。


总结HashMap 4 种遍历方式:迭代器、for、lambda、stream,以及具体的 7 种遍历方法,综合性能和安全性来看,我们应该尽量使用迭代器(Iterator)来遍历EntrySet的遍历方式来操作 Map 集合,这样就会既安全又高效了。



相关文章
|
存储 安全 Java
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
228 3
Java之HashMap详解
本文介绍了Java中HashMap的源码实现(基于JDK 1.8)。HashMap是基于哈希表的Map接口实现,允许空值和空键,不同步且线程不安全。文章详细解析了HashMap的数据结构、主要方法(如初始化、put、get、resize等)的实现,以及树化和反树化的机制。此外,还对比了JDK 7和JDK 8中HashMap的主要差异,并提供了使用HashMap时的一些注意事项。
414 2
Java之HashMap详解
|
6月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
354 3
|
存储 Java
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
152 1
|
10月前
|
存储 缓存 安全
Java HashMap详解及实现原理
Java HashMap是Java集合框架中常用的Map接口实现,基于哈希表结构,允许null键和值,提供高效的存取操作。它通过哈希函数将键映射到数组索引,并使用链表或红黑树解决哈希冲突。HashMap非线程安全,多线程环境下需注意并发问题,常用解决方案包括ConcurrentHashMap和Collections.synchronizedMap()。此外,合理设置初始化容量和加载因子、重写hashCode()和equals()方法有助于提高性能和避免哈希冲突。
603 17
Java HashMap详解及实现原理
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
4431 113
|
9月前
|
存储 监控 Java
《从头开始学java,一天一个知识点》之:数组入门:一维数组的定义与遍历
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问&quot;`a==b`和`equals()`的区别&quot;,大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 这个系列就是为你打造的Java「速效救心丸」!我们承诺:每天1分钟,地铁通勤、午休间隙即可完成学习;直击痛点,只讲高频考点和实际开发中的「坑位」;拒绝臃肿,没有冗长概念堆砌,每篇都有可运行的代码标本。明日预告:《多维数组与常见操作》。 通过实例讲解数组的核心认知、趣味场景应用、企业级开发规范及优化技巧,帮助你快速掌握Java数组的精髓。
221 23
|
监控 前端开发 Java
Java SpringBoot –性能分析与调优
Java SpringBoot –性能分析与调优
|
存储 Java 程序员
Java面试加分点!一文读懂HashMap底层实现与扩容机制
本文详细解析了Java中经典的HashMap数据结构,包括其底层实现、扩容机制、put和查找过程、哈希函数以及JDK 1.7与1.8的差异。通过数组、链表和红黑树的组合,HashMap实现了高效的键值对存储与检索。文章还介绍了HashMap在不同版本中的优化,帮助读者更好地理解和应用这一重要工具。
792 5
|
Java 程序员 编译器
Java|如何正确地在遍历 List 时删除元素
从源码分析如何正确地在遍历 List 时删除元素。为什么有的写法会导致异常,而另一些不会。
397 3