初识LinkedHashMap源码

来源:互联网 发布:淘宝商品详情页怎么做 编辑:程序博客网 时间:2024/04/30 11:54

LinkedHashMap是HashMap的子类。使用了hashMap的构造方法。内部结构是hashMap + 双向环形链表。

Entry结构:记录了前后结点,并且也继承hashMap的entry。

private static class Entry<K,V> extends HashMap.Entry<K,V> {     final K key;     V value;     Entry<K,V> next;     int hash;    // These fields comprise the doubly linked list used for iteration.    Entry<K,V> before, after;    Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {        super(hash, key, value, next);    }...}

初始化:初始化都调用了hashMap的构造方法,默认初始化accessOrder成员变量为false。accessOrder可形成两种模式,查询模式,插入模式。

/*** The iteration ordering method for this linked hash map: <tt>true</tt>* for access-order, <tt>false</tt> for insertion-order.** true模式时,查询操作会把此entry放在尾结点处,形成查询时间排序* false(默认)时,是按照entry的插入顺序查出*/private final boolean accessOrder;public  LinkedHashMap(int initialCapacity, float loadFactor){          super(initialCapacity, loadFactor);          // 把accessOder模式赋值为false          accessOrder = false;     }LinkedHashMap(int initialCapacity)LinkedHashMap()LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)

利用模板模式,重写了hashMap的init方法。

// hashMap类public HashMap(int initialCapacity, float loadFactor) {    if (initialCapacity < 0)        throw new IllegalArgumentException("Illegal initial capacity: " +                                          initialCapacity);    if (initialCapacity > MAXIMUM_CAPACITY)        initialCapacity = MAXIMUM_CAPACITY;    if (loadFactor <= 0 || Float.isNaN(loadFactor))        throw new IllegalArgumentException("Illegal load factor: " +                                          loadFactor);    this.loadFactor = loadFactor;    threshold = initialCapacity;    init();}// LinkedHashMap 类private transient Entry<K,V> header;void init() {    // 初始化头结点hash值为-1。    header = new Entry<>(-1, null, null, null);    // 形成双链表结构    header.before = header.after = header;}

这里写图片描述

LInkedHashMap提供了4种初始化重载方式。内部调用了hashMap构造方法,赋值accessOrder模式,默认为false,可通过构造方法更改为true。重写了init方法,初始化一个双链表header头结点(hash值为-1,其他成员变量为null)。

get操作:

public V get(Object key) {    // 调用hashMap.get方法查找出节点位置    Entry<K,V> e = (Entry<K,V>)getEntry(key);    if (e == null)        return null;    // 查询后对列表进行调整    e.recordAccess(this);    return e.value;}// 是否把节点移到尾处void recordAccess(HashMap<K,V> m) {    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;    // accessOrder默认为false    if (lm.accessOrder) {        lm.modCount++;        // 移除这个节点        remove();        // 添加此节点在尾节点        addBefore(lm.header);    }}/*** 假设列表:1->2->3  1<-2<-3 删除2节点*               |   |*               1   3*         before = 1, after = 3*         1.after = 3  // 1->3*         3.before = 1 // 1<-3*     把[2节点之后3节点]赋值给[2节点之前1节点]的后节点*     把[2节点之前1节点]赋值给[2节点之后3节点]的前节点(发现数据结构的学习还是有用的!)*/private void remove() {    before.after = after;    after.before = before;}/*** 假设列表:header->1->3  header<-1<-3  添加2节点(this=2)**     2.after = header          // 2->header*     2.before = header.before  // 2<-3     3->2->header**     header.bef = 2**/// existingEntry = lm.header;private void addBefore(Entry<K,V> existingEntry) {    after  = existingEntry;    before = existingEntry.before;    before.after = this;    after.before = this;}

这里写图片描述

get操作是调用hashMap的get方法,查找后根据accessOrder值判断是否对双向链表进行调整。把查找节点移动到链表尾处。其中remove方法是移除这个节点在双向链表中,addBefore方法是把当前节点放到链表尾
(其实双向链表是一个环形的,以header节点为头结点,则新加入元素就在链表尾了)

put方法:
LinkedHashMap并没有重写hashMap的put方法。其内部还是调用还是调用了hashMap的put方法。
当put的key已经存在时,当前Entry会调用LinkedHashMap中Entry的recordaccess方法。这里会如果accessOrder的值为true时,会把当前查找entry放在双链表尾处。
当put的key不存在时,需要新创建Entry,调用LinkedHashMap的addEntry方法,首先会调用hashMap的addEntry方法,保证hashMap的正常逻辑。然后会调用removeEldestEntry方法判断是否删除老元素,这也就是LinkedHashMap可以实现LRU缓存的原理。(Least recent used的缩写,最近最少使用)我们可以通过重写这个removeEldestEntry返回true和初始化设置recordAccess为true来实现。
之前调用HashMap的addEntry方法还没有走完,里面会判断是否需要扩容,如果需要扩容,会调用LinekHashmap的transfer进行数据的转移,transfer里使用header双向列表重新hash插入table中,比hashMap的方法效率高,减少查询次数。
如果不需要扩容,直接调用LinkedHashMap的createEntry方法去添加节点。

ps:这里并没有写入过多的代码,需要读者自己去查看本机源码,jdk1.7.
这里写图片描述

这里写图片描述

总结:
LinkedhashMap大多方法使用的是hashMap,底层是Entry增加了before和after两个节点,把所有节点以环形双链表结构再连接起来。
可用来实现LRU最近最少使用缓存原则,是accessOrder成员变量和removeEldestEntry方法。accessOrder可通过构造方法改变为true,在put和get时把当前Entry放在队尾(严格来讲没有队尾,因为是环形,以header为参照物),removeEldestEntry可通过重写返回true,删除header节点之后一个元素。以此来删除最不长使用元素。awayls put /get last out 。
get方法是使用hashMap方法的get查找方式,先找到table中的位置,再循环链表。LinkedList增加accessOrder操作。
put中重写了record access,AddEntry,CreateEntry,Transfer方法。
put方法重写recordaccess方法增加accessOrder操作。AddEntry时是否删除过期节点。CreateEntry在table中和双链表结构中增加相同节点。Transfer利用循环双链表扩容,减少操作,提高效率。


Ps:LinkedHashMap的源码研究到这里就先截止了,生命还有很多事要做,技术上还有很多要学,其中大部分的知识理论也是从网上众多博客参考而来,但完全不是照抄,是有个人理解的。双向环状链表是真的手写bug才看出来的,博客里有说头插有说尾插的。文章中有很多知识点重复出现,是为了让大家能够记住,采用总分总的作文模式来写的。写这个技术文章,花了很长时间,也画了图解释说明,主要是想自己加深理解,希望能够帮到迷茫的同学们,个人还有很多不足,希望大家提出。

0 0