今天我们来探究一下Map吧!

根据Map源码上的注释可以得到:
1.Map是一个接口,他是key-value的键值对,一个map不能包含重复的key,并且每一个key只能映射一个value;
2.Map接口提供了三个集合视图:key的集合,value的集合,key-value的集合;
3.Map内元素的顺序取决于Iterator的具体实现逻辑,获取集合内的元素实际上是获取一个迭代器,实现对其中元素的遍历;
4.Map接口的具体实现中存在三种Map结构,其中HashMap和TreeMap都允许存在null值,而HashTable的key不允许为空,但是HashMap不能保证遍历元素的顺序,TreeMap能够保证遍历元素的顺序。
好了说到这里我们不得不探究一下HashMap这个东西了![]()
HashMap是基于哈希表的Map接口的实现,提供所有可选的映射操作,允许使用null值和null键,存储的对象时一个键值对对象Entry<K,V>;
是基于数组+链表的结构实现,在内部维护这一个数组table,数组的每个位置保存着每个链表的表头结点,查找元素时,先通过hash函数得到key值对应的hash值,再根据hash值得到在数组中的索引位置,拿到对应的链表的表头,最后去遍历这个链表,得到对应的value值。
HashMap存储的对象是一个Entry实体,Entry是HashMap中的一个静态内部类
好了说了这么多概念,大家看到都看过的,那么我们今天探究的重点是map的循环遍历---->>>







![]()
我们在JDK8之前,可以使用keySet或者entrySet来遍历HashMap,JDK8中引入了map.foreach来进行遍历的方式就方便太多了。 感谢jdk8![]()
首先我们先说一下遍历的效率问题吧:
keySet其实是遍历了2次,一次是转为Iterator对象,另一次是从 hashMap中取出key所对应的value。而entrySet只是遍历了一次就把key和value都放到了entry中,效率更高。如果是JDK8,使用Map.foreach方法,foreach本质上也是entrySet遍历方式。
首先我们来看一下这三个遍历方式的源码吧
:
public Set<Map.Entry<K,V>> entrySet() {Set<Map.Entry<K,V>> es;return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;}final class EntryIterator extends HashIteratorimplements Iterator<Map.Entry<K,V>> {public final Map.Entry<K,V> next() { return nextNode(); }}
public Set<K> keySet() {Set<K> ks = keySet;if (ks == null) {ks = new KeySet();keySet = ks;}return ks;}final class KeyIterator extends HashIteratorimplements Iterator<K> {public final K next() { return nextNode().key; }}
default void forEach(BiConsumer<? super K, ? super V> action) {Objects.requireNonNull(action);for (Map.Entry<K, V> entry : entrySet()) {K k;V v;try {k = entry.getKey();v = entry.getValue();} catch(IllegalStateException ise) {// this usually means the entry is no longer in the map.throw new ConcurrentModificationException(ise);}action.accept(k, v);}}
看了这些源码感觉是不是很糟心啊?


那下来就看看map遍历时的使用大家就一目了然了


清晰明了![]()
![]()
最后补充下map的重要点吧:
1.Java8中HashMap不支持线程安全的问题已经存在很多年了,官方也没有打算去解决其死循环的问题,对于需要线程安全的场景,官方更推荐使用ConcurrentHashMap类。
2.resize扩容时死循环,在扩容操作时,是对链表进行循环操作,如果同时有两个线程在对同一个链表进行transfer操作,线程A在transfer的时候会修改为value2.next=value1, 线程B操作时,根据原始链表拿到的是value1.next=value2,而由于线程A已经修改为value2.next=value1,那么就会存在死循环的问题。
3.HashMap是由数组和链表组成的,数组是HashMap的主体,链表是为了解决哈希冲突的问题,对于HashMap的插入问题,如果插入位置不含有链表,那么直接插入到链表的表头即可,如果包含链表,需要先遍历链表,判断key是否已经在链表中存在,存在则替换value值,不存在则插入到链表的表头。

本文详细探讨了Map接口及其核心实现HashMap的特点与工作原理,包括Map的三种集合视图、HashMap的哈希表结构、存储机制及遍历方式。特别介绍了HashMap在JDK8中的foreach遍历方法,对比了不同遍历方式的效率,并讨论了HashMap的线程安全问题及扩容时可能遇到的死循环风险。
1177





