java8中linkedhashmap源码分析

2022-10-23  本文已影响0人  隔壁小新
大纲

1.linkedhashmap数据结构原理

2. linkedhashmap源码分析

2.1 linkedhashmap成员变量参数
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{
    
    // 序列化唯一表示 UID
    private static final long serialVersionUID = 3801124242820219131L;

    // 双向链表的头节点
    transient LinkedHashMapEntry<K,V> head;

    // 双向链表的尾节点
    transient LinkedHashMapEntry<K,V> tail;

    // 默认是 false,则迭代时输出的顺序是插入节点的顺序。
    // 若为 true,则输出的顺序是按照访问节点的顺序(最近最少使用原则)。
    // accessOrder 为 true 时,可以在这基础之上构建一个 LruCache。
    final boolean accessOrder;

}
2.2 linkedhashmap构建方法
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{
    
    ...
    
    /**
     * 无参构造函数
     */
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

    /**
     * 有参构造函数
     *
     * @param initialCapacity 初始化时的容量
     */
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

    /**
     * 有参构造函数
     *
     * @param initialCapacity 初始化时的容量
     * @param loadFactor      扩容的加载因子
     */
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

    /**
     * 有参构造函数
     *
     * @param initialCapacity 初始化时的容量
     * @param loadFactor      扩容的加载因子
     * @param accessOrder     迭代输出节点的顺序
     */
    public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

    /**
     * 有参构造函数
     *
     * @param m 利用另一个Map 来构建
     */
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        // 批量插入一个 map 中的所有数据到本集合中。
        putMapEntries(m, false);
    }
    ...
    
}

2.3 .linkedhashmap的put()方法。
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }
 
 
    TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
        TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
        linkNodeLast(p);
        return p;
    }
 
    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;
        }
    }
 
    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中的Node方法,当创建的时候会调用当前方法中的类进行创建,根据添加顺序为双向链表添加位置。在hashmap的put()调用中会生成前后项指针。当前情况都是

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
            ...
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e); //⭐️⭐️⭐️⭐
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

linkedhashmap直接使用了hashmap的put()方法,因为继承了hashmap并且重写了afterNodeInsertion()方法,在accessOrder为true的时候调用,会破坏原先的顺序存储,会把最近访问的放到链表的最后

    /**
 * 此方法的作用是将刚刚访问的节点e放到链表的尾端
 */
void afterNodeAccess(Node<K,V> e) {
    LinkedHashMap.Entry<K,V> last;
    // accessOrder = true 时 访问节点后才需要置于尾端
    // 如果e本身就在尾端,那就不需要操作
    if (accessOrder && (last = tail) != e) {
        // 记录节点e、e的前驱、e的后继
        LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        //   b<->p  <--> a
        // 第一步:现将p.after置空
        //断开p的后节点
        p.after = null;
        // 第二步:将e的前驱.after 连接上e的后继
        if (b == null)
          //如果没有头节点      b<---> p  
          直接把head指向a
            head = a;
        else
          //如果有前驱的话,把b<  ---> a
            b.after = a;
        if (a != null)
              // 如果a不等于空的话,把a指向b
            a.before = b;
        else
            //如果a等于空的话,把last指向b.
            last = b;
        if (last == null)
          //如果last 空把p插入到head节点。
            head = p;
        else {
          //把p插入到最后节点。
            p.before = last;
            last.after = p;
        }
      //更新节点为尾节点。
        tail = p;
        // 链表结构性调整,修改次数自增
        ++modCount;
    }
}
2.4.linkedhashmap的remove方法
    void afterNodeRemoval(Node<K,V> e) { // unlink
        LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; // b是当前节点的前一个节点,a是后一个节点
        p.before = p.after = null; //先断开当前节点,把当前节点对上一个和下一个节点的引用置为空
        if (b == null) //当前节点的前一个节点是null,说明当前节点是头节点,那去掉当前节点之后,当前节点的后一个节点成为了链第一个,
            // 也就是头节点,当然有可能a也是null,那整个链就是空链,这种写法兼容了a也是null的情况
            head = a;
        else 
            b.after = a; //如果当前节点不是头节点,直接去掉当前节点,当前节点的前一个和后一个连起来
        if (a == null) //如果当前节点的后一个节点是null,说明当前节点是尾节点,那把当前节点去掉后,当前节点的前一个节点成为了链的最后一个节点尾节点。
            tail = b;
        else
            a.before = b;//如果当前节点不是尾节点,直接去掉当前节点,当前节点的前一个和后一个连起来
    }

当前方法总结:当前方法为删除双向链表的节点:有一下几种情况: a 前一个节点; b后一个节点;p当前节点;
1.a<-->p --> a ;

  1. a<--> p <-->b --> a<-->b;
  2. p <--> b --> b;
2.5. linkedhashmap的get()方法,
    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;
    }

与hashmap的区别在于,当accessOrder为true的话,会把当前访问的节点插入到双向链表的最后。
喜欢的话点歌赞个👍

上一篇下一篇

猜你喜欢

热点阅读