java Map探秘总结

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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天我们来探究一下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 HashIterator        implements 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 HashIterator        implements 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值,不存在则插入到链表的表头。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值