浅析linkedHashMap双向链表与Lru算法的关系

 LinkedHashMap继承HashMap,与HashMap的不同主要是它重写了父类的Entry这个类,以及addEntry(),recordAccess()等方法。
  1. HashMap是单向的链表,因为他的保存对象Entry中只有保存Entry next,迭代输出时按默认的插入顺序
  2. linkedHashMap是双向链表,因为他重写了父类Entry增加了Entry before以及Entry after,迭代输出时可以按插入顺序,也可以按访问顺序
Entry:是链表保存的实体对象,其中包含了key,value,hash,entry等。Entry对象的保存是最终以数组table[]的形式保存在该数组中,并且根据相关的hash值可以确定每          个Entry对应的数组下标index。

     分析linkedHashMap要从put方法开始,linkedHsapMap的put方法直接使用的是父类HashMap的put方法,
public V put(K key, V value) {
    if (table == EMPTY_TABLE) { // 如果初始数组为空,就初始化一个容量,并且实例化一个数组 table = new Entry[capacity];
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
    int i = indexFor(hash, table.length);// 根据产生的相关Hash值确定Entry的下标
    for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue; // 此时返回旧的数据,初次put的时候 不进入,返回为null
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

继续分析,调用的是addEntry的方法,此时需要注意子类LinkedHashMap重写了该方法
这里注意下父类的构造方法中有个inti();方法,该方法并未具体实现逻辑而是交给子类去重写,linkedHashMap的init()方法初始化了一个Header,实际就是实体Entry,并且他的before和after都是自己。
@Override
void init() {
    header = new LinkedHashMapEntry<>(-1, null, null, null);
    header.before = header.after = header;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
    // Previous Android releases called removeEldestEntry() before actually
    // inserting a value but after increasing the size.
    // The RI is documented to call it afterwards.
    // **** THIS CHANGE WILL BE REVERTED IN A FUTURE ANDROID RELEASE ****

    // Remove eldest entry if instructed
    LinkedHashMapEntry<K,V> eldest = header.after; 
    if (eldest != header) {  //如果链表header后面有数据就进入
        boolean removeEldest;
        size++;
        try {
            removeEldest = removeEldestEntry(eldest); //调用此方法判断是否要删除头部数据 也就是最老的数据
        } finally {
            size--;
        }
        if (removeEldest) {
            removeEntryForKey(eldest.key);
        }
    }

    super.addEntry(hash, key, value, bucketIndex);  //调用父类的创建方法
}

接下来父类的addEntry会调用子类的creatEntry的方法:
void createEntry(int hash, K key, V value, int bucketIndex) {
    HashMapEntry<K,V> old = table[bucketIndex];
    LinkedHashMapEntry<K,V> e = new LinkedHashMapEntry<>(hash, key, value, old);
    table[bucketIndex] = e;
    e.addBefore(header);
    size++;
}
代码中主要关键的还是addBefore()这个方法:
private void addBefore(LinkedHashMapEntry<K,V> existingEntry) {
    after  = existingEntry;
    before = existingEntry.before;
    before.after = this;
    after.before = this;
}
将初始化的header代入赋值,最终会形成双向链表的关系:

linkedHashMap的主要一个作用是它具有HashMap没有访问顺序,按访问顺序迭代,超出数量限制后移除最老的元素是Lru算法的主要原理!
那是如何实现这个访问顺序呢?我们发现在第二次put(而不是初次put操作)或者get的时候,都会调用一个方法  e.recordAccess( this);
看源码,这个是Entry实体的方法,字面意思是重新将链表排序
/**
* This method is invoked by the superclass whenever the value
* of a pre-existing entry is read by Map.get or modified by Map.set.
* If the enclosing Map is access-ordered, it moves the entry
* to the end of the list; otherwise, it does nothing.
*/
void recordAccess(HashMap<K,V> m) {
    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
    if (lm.accessOrder) {
        lm.modCount++;
        remove(); // 首先执行Remove方法
        addBefore(lm.header); // 再调用addbefore方法重新加到链表的尾部去,此时最新的数据将会保留在链表尾部!
    }
}
remove方法的作用是将被访问的该实体的before与after两个实体连接起来
/**
* Removes this entry from the linked list.
*/
private void remove() {
    before.after = after;
    after.before = before;
}
经过这么处理之后,最老的数据也就停留在了链表头部,最新的数据就在链表尾部!记住这层关系!
实现lru算法是我们在每次put元素进入时,都会检查一下removeEldestEntry ,如果返回为true就会触发移除老数据的方法,如下:
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}
将被移除的数据保留在链表头部,也就是会移除最老的数据!


     
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值