初识LinkedHashMap源码

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值