LinkedHashMap原理分析笔记

一、最基本元素存储单元

 /**
     * 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);
        }
    }
 还是原HashMap的,多了两个引用before,after,说明这是要用双向链表

 

二、初始化 构造方法

public LinkedHashMap() {
        super();
        accessOrder = false;
    }
    多了 accessOrder = false;
    
     /**
     * 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表示迭代顺序,true表示访问顺序,false表示插入顺序。   

 

三、put方法

put方法没有重写,因此和HashMap是一样的,但也有不同,不同在于LinkedHashMap实现了afterNodeAccess,afterNodeInsertion方法
看HashMap put源码

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);  //这个方法HashMap是空实现,这里是发生hash冲突后,找到有相同key对值进行处理时调用
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);   //这个方法HashMap也是空实现,这里是完成新数据put后调用
        return null;
    }



LinkedHashMap具体实现 :
1.void afterNodeAccess(Node<K,V> e) { // move node to last 注释就说明了是把该元素移到最后 LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { //accessOrder用到了,默认false等于不运行,true时是按插入顺序 LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; 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; } } 以上代码看出在按插入顺序时,在有相同key时,对当前节点将其移到链表末尾 2.void afterNodeInsertion(boolean evict) { // possibly remove eldest LinkedHashMap.Entry<K,V> first; if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } } removeEldestEntry 方法在jdk1.8中固定返回false,因此不用关注此方法 说明一下removeElestEntry用于定义删除最老元素的规则。一旦需要删除最老节点,那么将会调用removeNode删除节点。
举个例子,如果一个链表只能维持100个元素,那么当插入了第101个元素时,以如下方式重写removeEldestEntry的话,那么将会删除最老的一个元素


 

 四、get方法(get进行了重写) ,在插入模式中,获取值后又一次进行把当前节点移到链表尾部操作

  有点类似LRU(最少最近使用)算法~

 /*基于访问排序*/
        LinkedHashMap<String, String> linkedHashMap_accessOrder = new LinkedHashMap<>(10,0.75f,true);
        linkedHashMap_accessOrder.put("name1", "josan1");
        linkedHashMap_accessOrder.put("name2", "josan2");
        linkedHashMap_accessOrder.put("name3", "josan3");
        linkedHashMap_accessOrder.put("name4", "josan4");
        linkedHashMap_accessOrder.put("name5", "josan5");

        linkedHashMap_accessOrder.get("name5");
        linkedHashMap_accessOrder.get("name1");
        linkedHashMap_accessOrder.get("name2");
        linkedHashMap_accessOrder.get("name4");
        linkedHashMap_accessOrder.get("name3");
        //51243顺序
        for (Map.Entry<String, String> entry : linkedHashMap_accessOrder.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println("key:" + key + ",value:" + value);
        }

get方法: (accessOrder 为true表示按照访问排序),

  public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

 将节点移动到最后:

 void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            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;
        }
    }

层层判断和更改引用之后,p已经独立出来了,就把p移到了map的末尾,实现了访问排序

 

总结

1. HashMap中TreeNode 即红黑树的节点类是继承LinkedHashMap.Entry

2. HashMap中没有使用双向链表,before, after没有使用,单纯的红黑树

3.LinkedHashMap是HashMap的子类,很多方法都是继承自父类, 

   LinkedHashMap中,存取等使用的也使用红黑树,但维护了before, after的链表属性,在存取时一样使用红黑树算法,

  但keySet()、values()以及entrySet()等迭代时使用的是双向链表来进行的,这样的双向链表结构保证了插入顺序的有序。

 4.总得来说,LinkedHashMap底层是数组加单项链表加双向链表

 5.数组加单向链表就是HashMap的结构,记录数据用,双向链表,存储插入顺序用。然后LInkedHashMap重写了两个方法,一个是添加entry,一个是创建entry,这两种时刻都要维护双向链表。

 

  

 

 

参考出处:https://www.cnblogs.com/zhaojj/p/7832680.html

posted @ 2019-06-18 09:15  将军上座  阅读(176)  评论(0编辑  收藏  举报