• Java源码阅读LinkedHashMap


    1类签名与注释

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

    哈希表和链表实现的Map接口,具有可预测的迭代次序。 这种实现不同于HashMap,它维持于所有entrys的双向链表。

    此类提供了所有可选的Map操作,并允许空元素。 像HashMap,它提供了基本操作(add,containsremove)稳定的性能。

    性能可能略低于HashMap ,这是由于维护链表的额外费用。但是有一个例外:LinkedHashMap的收集视图的迭代器与map的size成正比,而与容量无关。 HashMap的迭代可能更昂贵,与其容量成正比。

    与HashMap一样,影响其性能的两个因素:初始容量、负载因子。但是对于LinkedHashMap来说初始容量选高一点对性能的影响不太严重,前面也说了其迭代和容量无关。

    注意区别size与capacity,前者是集合里面的实际值的数量,后者是集合的容量。

    请注意,此实现不同步。 如果多个线程同时访问链接的散列映射,并且至少一个线程在结构上修改映射,则必须在外部进行同步。 这通常通过在自然地封装地图的一些对象上同步来实现。 如果没有这样的对象存在,应该使用Collections.synchronizedMap方法“包装”map。 这最好在创建时完成,以防止意外的不同步访问map:

    Map m = Collections.synchronizedMap(new LinkedHashMap(...)); 

    该类所有集合视图方法返回的iterator方法返回的迭代器是快速失败的:如过再迭代过程中,除了迭代器的remove之外的任何方法修改了集合结构,迭代器会马上抛出一个ConcurrentModificationException异常。

    (2)数据结构

    LinkedHashMap是基于HashMap实现的,不同的是在其Entry内部加了before和after节点,然后在LinkedHashMap类里面加了head和tail节点。

    下面先是LinkedHashMap的Entry实现

    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);
            }
        }

    LinkedHashMap.Entry继承自HashMap的Entry(Node是HashMap里面对Entry的具体实现)。

    下面是LinkedHashMap与HashMap相比特有的属性

    transient LinkedHashMap.Entry<K,V> head;
    
    transient LinkedHashMap.Entry<K,V> tail;
    
    final boolean accessOrder;

    head记录双向链表的头节点

    tail记录双向链表的尾节点

    accessOrder规定了该链表的迭代顺序:false表示insertion-order(默认),true表示access-order

    关于LinkedHashMap的构造器是调用其父类的构造器实现的,这里就不多做介绍了。

    3常用方法

    (1)containsValue方法

    public boolean containsValue(Object 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;
        }

    containsValue的作用是查找该map是否有key值映射该value,若是则返回true,否则返回false。

    这里理解一点map集合的所有entry都串起来成为了一个双向链表,可以通过befor向前遍历和也可以通过after向后遍历(containsValue就是通过after向后遍历的)。

    注意区别after与next:

    通过next将同一个存储桶里(key计算的hash值相同)的entry串成一个单向链表,这是HashMap里面实现的数据结构。

    after只是指下一个节点,可能是当前存储桶的(和next指向同样的值),也可能是其他存储桶的节点(和next指向不同值),这是在LinkedHashMap中实现的。

    (2)get方法

    1 public V get(Object key) {
    2         Node<K,V> e;
    3         if ((e = getNode(hash(key), key)) == null)
    4             return null;
    5         if (accessOrder)
    6             afterNodeAccess(e);
    7         return e.value;
    8     }

    默认情况(accessOrder为false,也就是insertion-order),通过HashMap的getNode方法找到对应的节点,然后将其value返回。但是当accessOrder为true,也就是access-order时,在返回找到的节点的value之前,会将该节点移动到双向量表的链尾。afterNodeAccess实现了该操作(链表的基本操作,这里就不贴代码了)。

    (3)put方法

    LinkedHashMap没有实现自己的put方法,而是从其父类HashMap继承过来的。那么问题来了,HashMap并没有维护双向链表,LinkedHashMap插入的时候是如何构造双向链表的?

    首先回顾一下HashMap的put

     1 public V put(K key, V value) {
     2         return putVal(hash(key), key, value, false, true);
     3     }
     4 
     5 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
     6                    boolean evict) {
     7         Node<K,V>[] tab; Node<K,V> p; int n, i;
     8         if ((tab = table) == null || (n = tab.length) == 0)
     9             n = (tab = resize()).length;
    10         if ((p = tab[i = (n - 1) & hash]) == null)
    11             tab[i] = newNode(hash, key, value, null);
    12         else {
    13             Node<K,V> e; K k;
    14             if (p.hash == hash &&
    15                 ((k = p.key) == key || (key != null && key.equals(k))))
    16                 e = p;
    17             else if (p instanceof TreeNode)
    18                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    19             else {
    20                 for (int binCount = 0; ; ++binCount) {
    21                     if ((e = p.next) == null) {
    22                         p.next = newNode(hash, key, value, null);
    23                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    24                             treeifyBin(tab, hash);
    25                         break;
    26                     }
    27                    //省略部分代码 afterNodeInsertion(evict);
    28     }
    29 
    30 Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
    31         return new Node<>(hash, key, value, next);
    32     }

    每次插入新的节点都是通过newNode(此处省略红黑树,按照之前jdk版本的来:数组/链表实现HashMap),HashMap自己的newNode方法是new一个自己的Node对象。而LinkedHashMap也实现了newNode方法,如下

     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;
        }

    LinkedHashMap的newNode主要分两步:首先new一个LinkedHashMap.Entry的对象p,然后调用linkNodeLast在双链表尾部插入新的p节点。而通过LinkedHashMap的对象调用put方法,put方法内部则会调用LinkedHashMap的newNode方法,而不是HashMap的newNode(这好像就是灵活的java多态)linkNodeLast的具体实现如下

     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;
            }
        }

    linkNodeLast把待插入的节点放到尾节点,然后维护before和after引用。这样确保了LinkedHashMap的插入的顺序,当迭代该集合的时候也是按照该顺序来的。

    还有一点需要注意,当插入完成后会调用afterNodeInsertion。该方法在LinkedHashMap的实现:当参数为true时删除最老的节点,按照插入顺序的话也就是头节点。

    (4)remove方法

    同理,LinkedHashMap没有实现自己的remove方法,也是从其父类HashMap继承过来的。

    HashMap的remove主要流程如下

    public V remove(Object key) {
            Node<K,V> e;
            return (e = removeNode(hash(key), key, null, false, true)) == null ?
                null : e.value;
        }
    
     final Node<K,V> removeNode(int hash, Object key, Object value,
                                   boolean matchValue, boolean movable) {
           //省略部分代码
           //该部分工作是找到待删除节点node,并删除
                    afterNodeRemoval(node);
            //...
        }
    
    // Callbacks to allow LinkedHashMap post-actions
    void afterNodeRemoval(Node<K,V> p) { }

    HashMap的remove找到待删除节点node,将其删除之后还会调用afterNodeRemoval方法,该方法在HashMap中的什么都没做,并且官方注释清楚的指出就是用来LinkedHashMap做一些后置行动的(维护双向链表)。

    LinkedHashMap中的afterNodeRemoval实现如下

     void afterNodeRemoval(Node<K,V> e) { 
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.before = p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a == null)
                tail = b;
            else
                a.before = b;
        }

    将e节点的前一个节点的after指向e的后一个节点,将e的后一个节点的before指向e的前一个节点。

  • 相关阅读:
    0502-计算图
    0601-利用pytorch的nn工具箱实现LeNet网络
    0501-Variable
    0201-PyTorch0.4.0迁移指南以及代码兼容
    0401-Tensor的基础操作
    0303-利用手写数字问题引入深度神经网络
    0302-利用pytorch解决线性回归问题
    ZT台式机 Tensorflow配置
    java计算日期之间的时间差并转为毫秒
    sklearn cluster KMeans
  • 原文地址:https://www.cnblogs.com/ouym/p/9010620.html
Copyright © 2020-2023  润新知