• Java集合——LinkedHashMap源码详解


    0. 前言

    转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52684341

    通过HashMap、HashTable以及ConCurrentHashMap异同比较一文我们了解了HashMap的内部存储结构以及各种特性,与HashMap相比,因为LinkedHashMap是继承自HashMap,因此LinkedHashMap

    1)同样是基于散列表实现。

    2)同时实现了Serializable Cloneable接口,支持序列化和克隆。

    3)并且同样不是线程安全的。

    区别是其内部维护了一个双向循环链表,该链表是有序的,可以按元素插入顺序或元素最近访问顺序(LRU)排列。

    我们在常见的内存泄漏以及解决方案(二)中介绍的LruCache类就是基于LinkedHashMap实现的。

    LinkedHashMap 类层次结构如下所示:



    1.  LinkedHashMap数据存储格式

    下面这张图来自于BridgeGeorge的博客,省的自己画了。


    如上图所示,假设LinkedHashMap进行put操作分别将ABCDEFGHIGKL,共12KVLinkedHashMap不仅像HashMap那样对其进行基于哈希表和单链表Entry数组+ next链表的存储方式,而且还结合了LinkedList的优点,为每个Entry节点增加了前驱和后继,并增加了一个为null头结点,构造了一个双向循环链表

    也就是说,每次put进来KV,除了将其保存到对哈希表中的对应位置外,还要将其插入到双向循环链表的尾部。


    2.  LinkedHashMap的构造方法

    public LinkedHashMap(int initialCapacity, float loadFactor) {
            super(initialCapacity, loadFactor);
            accessOrder = false;
    }
    

    若未指定初始容量initialCapacity,则默认为使用HashMap的初始容量,即16。若未指定加载因子loadFactor,则默认为0.75

    accessOrder默认为faslse。这里需要介绍一下这个布尔值,它是双向链表中元素排序规则的标志位

    (1)accessOrder若为false,遍历双向链表时,是按照插入顺序排序。

    (2)accessOrder若为true,表示双向链表中的元素按照访问的先后顺序排列,最先遍历到(链表头)的是最近最少使用的元素

    后面会详细讲解这个标志位的作用原理。


    3.  LinkedHashMapput操作

    3.1  Key已存在的情况

    HashMapput方法中,在发现插入的key已经存在时,除了做替换工作,还会调用recordAccess()方法,HashMap中该方法为空LinkedHashMap覆写了该方法,(调用LinkedHashmap覆写的get方法时,也会调用到该方法),LinkedHashmap并没有覆写HashMap中的put方法,recordAccess()LinkedHashMap中的实现如下:

    void recordAccess(HashMap<K,V> m) {
        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
    //判断accessOrder是否为true
    //将当前访问的Entry放置到双向循环链表的尾部,以标明最近访问
        if (lm.accessOrder) {
            lm.modCount++;
            remove();
            addBefore(lm.header);
        }
    }
    //双向循环链表中,将当前的Entry插入到existing Entry的前面
    private void addBefore(Entry<K,V> existingEntry) {
          after  = existingEntry;
          before = existingEntry.before;
          before.after = this;
          after.before = this;
    }
    

    3.2  Key不存在的情况

    putEntry的过程中,如果发现key不存在时,除了将新Entry放到哈希表的相应位置,还会调用addEntry方法,它会调用creatEntry方法,该方法将新插入的元素放到双向链表的尾部,这样做既符合插入的先后顺序,又符合了访问的先后顺序。

    //覆写HashMap中的addEntry方法 
    //在插入的key不存在的情况下,要调用addEntry插入新的Entry  
    void addEntry(int hash, K key, V value, int bucketIndex) {
        super.addEntry(hash, key, value, bucketIndex);
    //如果有必要,则删除掉该近期最少使用的节点,  
         //这要看对removeEldestEntry的覆写,由于默认为false,因此默认是不做任何处理
         Entry<K,V> eldest = header.after;
         if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        }
    }
    
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
            return false;
    }
    
    void createEntry(int hash, K key, V value, int bucketIndex) {
          //创建新的Entry,和HashMap一样将其插入到哈希表的相应位置
          HashMap.Entry<K,V> old = table[bucketIndex];
          Entry<K,V> e = new Entry<>(hash, key, value, old);
          table[bucketIndex] = e;
          //并将其移到双向链表的尾部
          e.addBefore(header);
          size++;
    }
    

    在上面的addEntry方法中有一个removeEldestEntry方法,这个方法可以被覆写,比如可以将该方法覆写为如果设定的内存已满,则返回true,这样就可以将最近最少使用的节点header后的节点)删除掉。


    这里为了方便对比总结,我把accessOrder标志位的作用原理做了个表,描述了一些操作对双链表中数据结构的影响,哈希表中元素该怎么处理还怎么处理,和HashMap是一致的。


    从总结的上表来看,只要是put进来的新元素,不管accessOrder标志位是什么,均将新元素放到双链表尾部,并且可以在需要实现Lru算法时时覆写removeEldestEntry方法,剔除最近最少使用的节点

    还有两种情况,get获取元素、还有putKey已经存在的元素,即调用recordAccess的这两种情况下,这个时候标志位就起作用了,accessOrderfasle时,什么也不做,也就是说当我们放入已经存在Key的键值对或get操作时,它在双链表中的位置是不会变的accessOrder设置为true时,上述两种情况会将相关元素放置到双链表的尾部。在缓存的角度来看,这就是所谓的“脏数据”,即最近被访问过的数据,因此在需要清理内存时(添加进新元素时),就可以将双链表头节点(空节点)后面那个节点剔除。

     

    4.  LinkedHashMapget操作

    //覆写HashMap中的get方法,通过getEntry方法获取Entry对象
    public V get(Object key) {
         Entry<K,V> e = (Entry<K,V>)getEntry(key);
         if (e == null)
            return null;
         e.recordAccess(this);
         return e.value;
    }
    

    通过前面的分析,果然在get中,除了正常的get逻辑,还调用了recordAccess()方法,这个方法的逻辑我们刚刚分析过了,和put进的元素key冲突的情况是一样的,这里就不赘述了。


    5.  LinkedHashMap的清空操作

    //清空HashMap的同时,将双向链表还原为只有头结点的空链表
    public void clear() {
       super.clear();
       header.before = header.after = header;
    }
    


    6.  HashMapLinkedHashMap的关系和比较

    1LinkedHashMap继承自HashMapHashMap的属性它都有,什么线程不安全,支持null等等。

    2LinkedHashMapHashMap多维护了一个双向循环链表。很明显,如果前面写的你看懂了,那么LinkedHashMap中维护了数据的两种排序方式,一个是基于数据插入顺序,一种是基于Lru算法。这一条可以说是两者最主要的区别了吧。


    至此关于LinkedHashMap的源码分析介绍完毕。

    转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52684341



  • 相关阅读:
    java.lang.NoClassDefFoundError: org/apache/poi/ss/formula/udf/UDFFinder
    IntelliJ使用指南—— 深入了解IntelliJ的Web部署逻辑
    javascript深入理解js闭包
    git commit -F时用到的commit.log模板
    github上需要生成密钥对:ssh key
    windows和linux字体库位置
    .vimrc配置文件
    iw交叉编译
    libnl和libopenssl,hostapd交叉编译
    linux中断
  • 原文地址:https://www.cnblogs.com/qitian1/p/6461506.html
Copyright © 2020-2023  润新知