java Map探秘总结

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

发布了117 篇原创文章 · 获赞 37 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/samHuangLiang/article/details/100534208
今日推荐