Java集合框架之LinkedHashMap

starlin 1,070 2018-09-19

本文若无特殊说明,源码均为JDK1.8

概述

LinkedHashMap它继承自HashMap,实现了Map<K,V>接口。其内部还维护了一个双向链表,在每次插入数据,或者访问、修改数据时,会增加节点、或调整链表的节点顺序,以决定迭代时输出的顺序。默认情况下遍历的顺序是按照插入节点的顺序,这也是与HashMap最大的区别,也可以在构造时传入accessOrder参数,使得其遍历顺序按照访问的顺序输出(后面介绍)

因继承自HashMap,所以HashMap上文分析的特点,除了输出无序,其他LinkedHashMap都有,比如扩容的策略,哈希桶长度一定是2的N次方等等。
LinkedHashMap在实现时,就是重写override了几个方法。以满足其输出序列有序的需求。

源码分析

属性

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>{

    /**
     * Entry<K,V>继承自HashMap.Node<K,V>,在其基础上扩展了一下。改成了一个双向链表
     * HashMap.Node subclass for normal LinkedHashMap entries.
     */
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
    
    /**
     * 双向链表的头部
     * The head (eldest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> head;

    /**
     * 双向链表的尾部
     * The tail (youngest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> tail;

    /**
     * 默认是false,则迭代时输出的顺序是插入节点的顺序。若为true,则输出的顺序是按照访问节点的顺序。
     * The iteration ordering method for this linked hash map: <tt>true</tt>
     * for access-order, <tt>false</tt> for insertion-order.
     *
     * @serial
     */
    final boolean accessOrder;

}

构造函数

    /**
     * 空的构造函数,accessOrder默认为false
     * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
     * with the default initial capacity (16) and load factor (0.75).
     */
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

    /**
     * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
     * with the specified initial capacity and a default load factor (0.75).
     * 指定具体的初始容量,加载因子默认为0.75
     * @param  initialCapacity the initial capacity
     * @throws IllegalArgumentException if the initial capacity is negative
     */
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

    /**
     * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
     * with the specified initial capacity and load factor.
     * 指定具体的初始容量,加载因子
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

    /**
     * 指定具体的初始容量,加载因子和迭代输出节点的顺序
     * Constructs an empty <tt>LinkedHashMap</tt> instance with the
     * specified initial capacity, load factor and ordering mode.
     * 
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @param  accessOrder     the ordering mode - <tt>true</tt> for
     *         access-order, <tt>false</tt> for insertion-order
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

    /**
     * 利用另一个Map 来构建,
     * 默认加载因子(0.75)和初始容量
     * Constructs an insertion-ordered <tt>LinkedHashMap</tt> instance with
     * the same mappings as the specified map.  The <tt>LinkedHashMap</tt>
     * instance is created with a default load factor (0.75) and an initial
     * capacity sufficient to hold the mappings in the specified map.
     *
     * @param  m the map whose mappings are to be placed in this map
     * @throws NullPointerException if the specified map is null
     */
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

构造函数和HashMap相比,就是增加了一个accessOrder参数。用于控制迭代时的节点顺序。

新增

LinkedHashMap并没有重写任何put方法。但是其重写了构建新节点的newNode()方法.
newNode()会在HashMap的putVal()方法里被调用,putVal()方法会在批量插入数据putMapEntries(Map<? extends K, ? extends V> m, boolean evict)或者插入单个数据public V put(K key, V value)时被调用。

    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        // 构建新节点时,构建的是LinkedHashMap.Entry<K,V>,不再是Node
        LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }

    // 将新增的节点,连接在链表的尾部
    // link at the end of list
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        // 集合为空
        if (last == null)
            head = p;
        else {//将新节点连接在链表的尾部
            p.before = last;
            last.after = p;
        }
    }

删除

LinkedHashMap也没有重写remove()方法,因为它的删除逻辑和HashMap并无区别,但是他重写了afterNodeRemoval()方法,

// 删除节点e时,同步将e从双向链表删除
void afterNodeRemoval(Node<K,V> e) { // unlink
    LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    // 待删除节点 p 的前置后置节点都置空
    p.before = p.after = null;
    // 如果前置节点是null,则现在的头结点应该是后置节点a
    if (b == null)
        head = a;
    else
    // 否则将前置节点b的后置节点指向a
        b.after = a;
    //如果后置节点时null ,则尾节点应是b
    if (a == null)
        tail = b;
    else
    //否则更新后置节点a的前置节点为b
        a.before = b;
}

查找

LinkedHashMap重写了get()和getOrDefault()方法:

    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)//相比HashMap中的get方法,这里是LinkedHashMap增加的
            afterNodeAccess(e);
        return e.value;
    }

    /**
     * {@inheritDoc}
     */
    public V getOrDefault(Object key, V defaultValue) {
       Node<K,V> e;
       if ((e = getNode(hash(key), key)) == null)
           return defaultValue;
       if (accessOrder)////相比HashMap中的方法,这里是LinkedHashMap增加的
           afterNodeAccess(e);
       return e.value;
   }

来看看afterNodeAccess()方法,这个方法的主要作用会将当前被访问到的节点e,移动双向链表的尾部,其源码如下:

    void afterNodeAccess(Node<K,V> e) { // move node to last
        // 原尾节点
        LinkedHashMap.Entry<K,V> last;
        //如果accessOrder 是true ,且原尾节点不等于e
        if (accessOrder && (last = tail) != e) {
             //节点e转成双向链表节点p
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            //p现在是尾节点, 后置节点一定是null    
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

值得注意的是,afterNodeAccess()函数中,会修改modCount,因此当你正在accessOrder=true的模式下,迭代LinkedHashMap时,如果同时查询访问数据,也会导致fail-fast,因为迭代的顺序已经改变。

containsValue方法

重写了该方法,相比HashMap的实现,更为高效。

    /**
     * Returns <tt>true</tt> if this map maps one or more keys to the
     * specified value.
     *
     * @param value value whose presence in this map is to be tested
     * @return <tt>true</tt> if this map maps one or more keys to the
     *         specified value
     */
    public boolean containsValue(Object value) {
        //遍历一遍链表,去比较有没有value相等的节点,并返回
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
            V v = e.value;
            if (v == value || (value != null && value.equals(v)))
                return true;
        }
        return false;
    }

Demo

在每次插入数据,或者访问、修改数据时,会增加节点、或调整链表的节点顺序。以决定迭代时输出的顺序。

public class LinkedHashMapDemo {
    public static void main(String[] args) {
        Map<String,String> linkedHashMap = new LinkedHashMap();
        linkedHashMap.put("a", "val_a");
        linkedHashMap.put("b", "val_b");
        linkedHashMap.put("c", "val_c");
        linkedHashMap.get("a");
        Iterator<Map.Entry<String, String>> iterator = linkedHashMap.entrySet().iterator();
        System.out.println("不设置accessOrder");
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }


        linkedHashMap = new LinkedHashMap(10,0.75f,true);
        linkedHashMap.put("a", "val_a");
        linkedHashMap.put("b", "val_b");
        linkedHashMap.put("c", "val_c");

        linkedHashMap.get("a");
        linkedHashMap.get("b");
        linkedHashMap.put("d", "val_d");
        iterator = linkedHashMap.entrySet().iterator();
        System.out.println("设置accessOrder为true");
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

# java集合