LinkedHashMap与LRU——来自源码的启示
Posted zhaolide
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LinkedHashMap与LRU——来自源码的启示相关的知识,希望对你有一定的参考价值。
LinkedHashMap是HashMap的扩展,它根据元素的插入顺序或者访问顺序(accessOrderd属性指定),使用双向链表,将所有元素连接起来,使得对HashMap的遍历变得有序。
示意图如下:
(图片引用自:https://blog.csdn.net/justloveyou_/article/details/71713781)
为什么要设计这个类?
这个实现是为了解决HashMap和HashTable无序问题,而又不增加像TreeMap那样对树操作的高额成本。
LRU是一种缓存淘汰算法,LRU(Least recently used,最远使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。如果对LRU不清楚的同学可以参考这篇文章:https://blog.csdn.net/zhoucheng05_13/article/details/79829601
下面是一段LinkedHashMap源码的注释文档:
上面源码注释的意思是,LinkedHashMap的一个特殊的构造器LinkedHashMap(int,float,boolean)被用来创建一个从最远到最近被访问的访问顺序排序的LinkedHashMap。这样的map非常适合用于实现LRU缓存。
1. 通过构造器指定accessOrder为true
上面提到的构造器源码如下:
重点在于它设置了accessOrder属性,关于该属性的定义如下:
2. 在元素被访问后将其移动到链表的末尾
在元素被访问时,如果accessOrder为true,即该map通过访问顺序排序,零基础学雅思那么被访问的元素会被移动到链表的末尾,以get方法为例:
可以看到,在指定的key存在的情况下,会判断accessOrder的属性,如果为true,会调用afterNodeAccess(e)方法,该方法源码如下:
这个在源码官方文档中同样有详细的说明:
上面的文档可以分为几个点:
- 调用put、putIfAbsent、get、getOrDefault、compute、computeIfAbsent、computeIfPresent、merge方法会引起对相应Entry的访问。
- replace方法只有在value被替换时才算访问操作
- putAll方法对传入map中的每一个映射都产生了一次访问(根据map的Iterator顺序)。
除此以外,没有任何方法会产生对entry的访问。尤其需要注意的是!在集合视图中的操作不会影响背后Map的迭代顺序。
上面的规则都可以从源码中找打答案,以replace()方法为例(该方法在HashMap中):
从上面我们可以看到,只有当key存在,并对value进行了替换之后,才会调用afterNodeAccess(e); 方法,产生顺序的调整。
所以,归根结底,只有在源码中调用了afterNodeAccess(e);方法,才会调整节点顺序,即算作对entry的访问操作。
LinkedHashMap对LRU策略青睐有加,它专门为其设计了一个方法:
该方法的说明文档长达20余行,为了节省篇幅,这里就不贴出来了,下面我会对其一一说明。
首先,这个方法的作用是决定当有新元素插入时,是否要移除Eldest的元素。新航道托福它的调用时机是在新元素被插入到map中后,被put和putAll方法调用。文档中这样说道:“它为实现者提供了一个机会,可以在每次添加新条目时删除最老的条目。这在使用map做缓存时非常有用:它允许map通过删除最旧的元素来减少内存消耗。”下面是官方文档中举的一个简单例子:
这个例子的作用是,当map增长到100时,每次插入新的元素就删除一个最老的元素,使得容量始终保持在100.
这个方法没有直接修改map,而是通过返回值决定是否允许map修改自身。要在这个方法中直接修改map也是允许的,不过如果要这么做,那么必须返回false,以防止map被重复修改。
在默认的实现中,这个方法仅仅返回false,所以这个map表现的像一个普通的map,最老的元素永远不会被移除。如果需要实现特定的功能,我们需要向下面这样重写该方法:
常听说Map可以用于实现缓存,当阅读了这个类过后才有了直观的感受。从源码中的方法可以看出,该类的确是为缓存量身定制的。
以上是关于LinkedHashMap与LRU——来自源码的启示的主要内容,如果未能解决你的问题,请参考以下文章