Java(JDK1.9)集合框架归纳总结——Map接口继承体系

注:该系列文章总结自JDK1.9源码的文档注释和源码,我对源码的部分中文注释已经同步到Github:https://github.com/Dodozhou/JDK

Map接口实现概述

Map的实现体系中,常用的类和接口可以大致分为三层。下面一一说明这些类或接口的特点和作用。

第一层

第一层包含一个抽象类AbstractMap和一个接口SortedMap。AbstractMap简单实现了Map接口的大部分操作,降低了实现Map接口的难度,第二层的所有类均继承了该类,它是Map接口的框架性形式。SortedMap接口定义了有序Map需要遵循的规则和实现的方法,有序Map都需要实现该接口。

AbstractMap

该类提供了一个Map接口的框架性实现,降低了实现Map接口的难度。该Map实现允许存入null值。它实现了Map接口的绝大多数方法,如get(Object):

 /**
     * {@inheritDoc}
     *
     * 通过entrySet().iterator()对map进行遍历,如果key等于null,则找出ey为
     * null的映射,并返回它的value。如果key不等于null,则找到匹配的映射,并返回value。
     * 如果找不到匹配的key,则返回null。
     * 该方法花费线性时间,很多实现会重写该方法。
     * 由于该实现允许null值的插入,因此不可以仅仅通过返回值来判断key是否存在。这在Map接口中已经说过,
     * 可以结合containsKey方法进行判断。
     * @implSpec
     * This implementation iterates over {@code entrySet()} searching
     * for an entry with the specified key.  If such an entry is found,
     * the entry's value is returned.  If the iteration terminates without
     * finding such an entry, {@code null} is returned.  Note that this
     * implementation requires linear time in the size of the map; many
     * implementations will override this method.
     *
     * @throws ClassCastException            {@inheritDoc}
     * @throws NullPointerException          {@inheritDoc}
     */
    public V get(Object key) {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        if (key==null) {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (e.getKey()==null)
                    return e.getValue();
            }
        } else {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (key.equals(e.getKey()))
                    return e.getValue();
            }
        }
        return null;
    }

同map接口一样,继承该类需要至少实现两个构造函数,一个无参,一个以map为参数。

此外,该类还重写了继承自Object的equals(Object)、HashCode()、toString()、Clone()方法,使得这些方法适用于Map类型。以equals为例:

public boolean equals(Object o) {
        if (o == this) //引用判断
            return true;

        if (!(o instanceof Map)) //类型判断
            return false;
        Map<?,?> m = (Map<?,?>) o;
        if (m.size() != size())   //大小判断
            return false;

        try {
            for (Entry<K, V> e : entrySet()) { //对所有entry进行循环遍历
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key) == null && m.containsKey(key))) //对键判断
                        return false;
                } else {
                    if (!value.equals(m.get(key)))  //对值判断
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

SortedMap

该接口继承了Map接口,它定义了key有序的Map。key如果实现Comparable接口,那么可以使用自然顺序进行排序。否则,在创建该map是提供外部comparator。对该map返回的视图(entrySet, keySet and values 方法)进行迭代遍历时就是根据key的顺序进行。该类还额外提供了几种方法来利用key有序这个特性。

该接口要求所有的key都必须是可以进行比较的,要么通过k1.compareTo(k2),要么通过comparator.compare(k1, k2)进行比较。它同时还规定了所有通用的sorted map需要提供4个构造器,分别是:

  1. 无参数构造器,给子类调用
  2. 只含有一个Comparator的构造器
  3. 只含有一个Map类型的参数,根据Map的key的自然顺序排序
  4. 只含有一个SortedMap类型参数的构造器。

接口提供了几个根据key的范围返回submaps的方法。这些范围都是半闭半开的。可以通过m.subMap(low, high+”\0”)来获得全闭范围。或者通过m.subMap(low+”\0”, high)来获得全开范围。

该接口定义了几种利用key有序特性的方法:

  • subMap(K fromKey, K toKey):获取指定范围内的子Map视图。
  • headMap(K toKey)
  • tailMap(K fromKey)
  • firstKey():获取第一个key
  • lastKey():获取最后一个key

第二层

如最开始的结构图所示,所有的类都实现了AbstractMap,只有TreeMap实现了SortedMap。接下来对这些类的作用和原理进行一个简略的说明。

HashMap

HashMap是一个基于哈希表来存储键值对,以期获得O(1)的操作时间。它的底层实现时这样的:

  1. 定义了Node节点,该节点类似于链表节点。使用Node数组作为哈希表
  2. 在插入元素时,通过(n-1)&hash求得下标,并将其插入到该下标处。
  3. 如果下标处已经有元素了,那么就将元素插入到该哈希桶的链表头部。
  4. 执行删除时,同样通过(n-1)&hash求得下标,然后对该哈希桶的链表进行遍历,找到了就删除。

他有如下特点:

  1. 容量始终是2的整数次幂,默认容量是16.当用户通过构造函数指定初始容量时,该类会将其转化为大于它的最接近的2的幂。如用户输入30,那么初始容量会是32.具体转化方式参见:https://blog.csdn.net/zhoucheng05_13/article/details/79815803
  2. 默认的装填因子(load factor)是0.75.
  3. 它使用链地址法解决哈希冲突。
  4. 在哈希表大小大于68,且链的长度大于8时,HashMap会将链转化为红黑树结构,以提高查询效率。
  5. 在哈希表容量小于68,那么HashMap会对哈希表进行扩容,并重新哈希。
  6. 在冲突链的长度小于6时,HashMap又会将红黑树转化为链表形式。
  7. 对HashMap进行遍历时,所话的时间与capacity而不是size成正比。以为它需要遍历Node数组的每一个位置。
  8. 当容量达到阈值(load factor * capacity)时,将Node数组增大2倍(因为容量室长要为2的幂)。

WeakHashMap & IdentityHashMap

WeakHashMap的key只保留对实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,WeakHashMap也可能自动删除这些key所对应的key-value对。

在IdentityHashMap中,当且仅当两个key严格相等(key1==key2)时,IdentityHashMap才认为两个key相等;相对于普通HashMap而言,只要key1和key2通过equals()方法返回true,且它们的hashCode值相等即可。

更具体的信息参加博客:https://blog.csdn.net/zhoucheng05_13/article/details/79834024

HashTable

可以将HashTable理解为HashMap的线程安全版本,但是它们在哈希算法等方面还是存在一些不同。该列已经逐渐别废弃,在线程安全的场景下,JDK推荐使用HashMap代替Hashtable。如果需要一个线程安全的高并发实现,那么建议使用ComcurrentHashMap而不是该类。

HashTable具有如下特点:

  1. 通过在所有方法上添加Synchronized关键字进行同步锁定,因此线程安全,但效率很低
  2. 初始容量是11,如果用户指定了初始容量,那么HashTable不会做任何修改。
  3. load factor同样是0.75
  4. 使用Entry

TreeMap

TreeMap是一个基于NavigableMap的红黑树实现。该实现保证了containsKey、get、put、remove操作的log(n)时间界。

TreeMap最重要的特点是,使用了红黑树这种有序平衡二叉树来保持了key的顺序,同时又利用有序二叉树的常用操作的log(n)时间界。

注意,该方法不是同步的。如果要在多线程环境下使用,那么需要进行外部同步,通常是通过锁定一个外部对象,如果不存在这样的对象,那么应该使用Collections.synchronizedSortedMap进行包裹。

由该类集合视图方法返回的集合,它们的迭代器都是快速失败机制的。(快速失败机制有什么用?首先,快速失败是通过在每次进行操作前判断modCount是否等于expectedModCount,如果不等则说明集合在外部被修改了,那么抛出异常,终止遍历,将风险降到最小,而不是在未来的某一不确定的时间,冒任意不确定的风险。该机制并不可靠(为什么不可靠?存在不同步的并发修改时,不可能做出任何绝对的保证),不能依赖于该机制编程,官方文档中指明,该机制仅用于检测错误)

关于红黑树,我还不了解,因此这里就不多说了。

第三层

第三层是HashMap的子类,LinkedHashMap。
LinkedHashMap是通过链表的方式使得插入变得有序的Map接口实现。这个实现是为了解决HashMap和HashTable无序问题,而又不增加像TreeMap那样的成本。它基于HashMap,并在HashMap的基础上为每个节点增加了一个双向链,形成了一个双向链表,以此来保证了元素的有序。

**重点!**LinkedHashMap的一个特殊的构造器LinkedHashMap(int,float,boolean)被用来创建一个从最近最少到最近被访问的访问顺序排序的LinkedHashMap。这样的map非常适合用于实现LRU缓存。关于LinkedHashMap与LRU的关系,参见:https://blog.csdn.net/zhoucheng05_13/article/details/79832885

LinkedHashMap与HashMap的不同点有:

  • LinkedHashMap在视图上的迭代器需要与size成比例的时间,与容量无关。而HashMap的迭代器依赖于哈希桶的容量,这将花费比LinkedHashMap更多的时间。
  • LinkedHashMap的initial capacity和load facotry参数的定义与HashMap中一样。不过,需要注意的是,初始容量过高给LinkedHashMap带来的影响比HashMap要小得多。因为LinkedHashMap的遍历是通过链表完成(HashMap是通过遍历所有哈希桶,时间与容量成正比)。由于添加了链表,因此LinkedHashMap的性能会略低于HashMap。
  • LinkedHashMap可以通过accessOrder参数来选择两种不同的排序方式:accessOrder为true时,以元素的访问顺序排序。为false时(默认情况),以元素的插入顺序排序。

猜你喜欢

转载自blog.csdn.net/zhoucheng05_13/article/details/79842142