LinkedHashMap为何能够实现LRU缓存

前言

Jdk中HashMap作为高频使用的数据结构能够满足大多数业务场景,但要HashMap实现LRU缓存功能那就需要额外增加很多代码才能实现。如果想要按照插入顺序遍历HashMap同样需要额外的代码编写才能实现。本文重点讨论LinkedHashMap原理及其应用场景。

由于LinkedHashMap继承自HashMap,很多方法直接调用HashMap类中,因此阅读本前最好了解HashMap内部基本原理可以参考之前HashMap源码分析相关文章。

HashMap构造方法与内存空间分配

HashMap扩容之二进制方法妙用

LRU 介绍

LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。

在应用开发中使用缓存时同样用LRU来淘汰最久未被使用的缓存。实现LRU缓存时还要保留Map的特性 那么LinkedHashMap绝对是不错的选择。如果还需要定时清除过期缓存那么可以考虑Google提供的guava工具包里面包含了很多缓存功能的数据结构

LinkedHashMap 类分析

通过类图可以看到LinkedHashMap继承HashMap类实现Map接口。意味着LinkedHashMap对HashMap做了进一步的增强。

最主要变化为哈希节点的变化,LinkedHashMap的Node节点增加了两个指针分别指向前驱节点与后继节点,同时声明了双向链表的头节点与尾节点。 

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

transient LinkedHashMap.Entry<K,V> head;

transient LinkedHashMap.Entry<K,V> tail;

LinkedHashMap中还有一个很重要变量为accessOrder,该变量控制插入元素后整个链表地排序方式。

  • true   表示按照访问顺序排列
  • false 表示按照插入顺序排列

head 与tail指针指向可以随意调整来满足使用是遍历顺序。

重要方法分析 

LinkedHashMap构造方中无一例外都调用了父类HashMap中的构造方法,并设置accessOrder值,只有最后一个构造函数可以设置accessOrder其余都为默认值。

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

public LinkedHashMap(Map<? extends K, ? extends V> m) {
     super();
     accessOrder = false;
     putMapEntries(m, false);
}

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
     super(initialCapacity, loadFactor);
     this.accessOrder = accessOrder;
}

此时跟HashMap其实没有区别,真正区别在插入数据与访问数据时会操作链表,在HashMap中专门提供了三个回调方法供LinkedHashMap使用。

LinkedHashMap直接使用HashMap的put方法插入元素但override了get方法。通过调用HashMap的put方法然后进行回到 afterNodeAccess与afterNodeInsertion方法。在HashMap中这些回调方法默认都是空方法因此HashMap本身没有任何逻辑执行

public static void main (String []args){
    LinkedHashMap<String,Object> map = new LinkedHashMap<String,Object>();
    map.put("1",2);
}

下图为HashMap中putVal方法中回调执行逻辑。真正的逻辑交由子类实现。也可以自定义Map来扩展这些方法实现更多功能。

由于LinkedHashMap Node 类中加入了链表指针,因此LinkedHashMap override了HashMap中创建Node方法。

  • newNode 创建拉链类Node,并设置节点指针指向
  • replacementNode 替换节点 调整指针指向
  • newTreeNode 创建树形节点,设置指针指向
  • replacementTreeNode 替换树节点调整指针指向

 在HashMap 中 newNode存在默认实现,因此调用LinkedHashMap时由于子类覆写了该方法默认实现被覆盖。通过多态实现扩展性 

 LinkedHashMap中newNode方法增加了处理链表指向的方法linkNodeLast,该方法把节点放到双向链表的末尾。

由于HashMap中插入元素是无序存放的,但通过链表可以很容易记录插入顺序因此LinkedHashMap可以按照插入顺序遍历整个Map

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

HashMap 插入数据后回调 afterNodeAccess方法。该方法可以调整链表中节点顺序为访问顺序

 LinkedHashMap 调用get方法查询元素后同样会调用该方法调整元素顺序。

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

afterNodeAccess方法通过调整指定节点指针将该节点调整至头结点,这符合LRU算法最近被访问到的一定是热数据其顺序应该在前面,尾节点数据代表最近最久未被使用的数据应该被剔除。

void afterNodeAccess(Node<K,V> e) { // move node to last
     LinkedHashMap.Entry<K,V> last;
     if (accessOrder && (last = tail) != e) {
         LinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
         p.after = null;
         if (b == null)
             head = a;
         else
             b.after = a;
         if (a != null)
             a.before = b;
         else
             last = b;
         if (last == null)
            head = p;
         else {
            p.before = last;
            last.after = p;
         }
         tail = p;
         ++modCount;
     }
}

总结

LinkedHashMap作为HashMap的扩展提供了很多优秀的特性,其底层数据结构设计值得学习。在特殊的业务逻辑中LinkedHashMap是不错的选择

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值