LinkedHashMap与LRU——来自源码的启示

LinkedHashMap是HashMap的扩展,它根据元素的插入顺序或者访问顺序(accessOrderd属性指定),使用双向链表,将所有元素连接起来,使得对HashMap的遍历变得有序。
示意图如下:
LinkedHashMap示意图
(图片引用自: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)方法,该方法源码如下:

这个在源码官方文档中同样有详细的说明:

上面的文档可以分为几个点:

  1. 调用put、putIfAbsent、get、getOrDefault、compute、computeIfAbsent、computeIfPresent、merge方法会引起对相应Entry的访问。
  2. replace方法只有在value被替换时才算访问操作
  3. 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可以用于实现缓存,当阅读了这个类过后才有了直观的感受。从源码中的方法可以看出,该类的确是为缓存量身定制的。

猜你喜欢

转载自www.cnblogs.com/zhaolide/p/9759824.html
今日推荐