开源框架是如何使用设计模式的-MyBatis缓存机制之装饰者模式 (2)

这是MyBatis的基础缓存,套娃的基本得有它,它的核心就是个HashMap来作为缓存容器,其实现的Cache接口的几个核心方法也都是委托给了HashMap去做。

FifoCache

一个支持先进先出的缓存策略的MyBatisCache

private final Cache delegate; //维护一个key的双端队列 private final Deque<Object> keyList; private int size; public FifoCache(Cache delegate) { //通过构造函数,将Cache组合进来,取名”委托“ this.delegate = delegate; this.keyList = new LinkedList<>(); this.size = 1024; } @Override public void putObject(Object key, Object value) { //先走自己的增强 cycleKeyList(key); //真实的写缓存交给”委托“去做 delegate.putObject(key, value); } @Override public Object getObject(Object key) { return delegate.getObject(key); } @Override public Object removeObject(Object key) { return delegate.removeObject(key); } private void cycleKeyList(Object key) { //将新写的缓存key添加到双端队列末尾 keyList.addLast(key); // 如果key的大小大于了1024(构造函数中默认赋值1024)则会移除最早添加的缓存 // 1. 移除自身维护的key队列的队头 2.委托给“委托”去真实删除队头缓存对象 if (keyList.size() > size) { Object oldestKey = keyList.removeFirst(); delegate.removeObject(oldestKey); } }

以上就是MyBatis先进先出缓存的实现了,FifoCache维护了key的双端队列,每次写缓存的时候会判断大小如果大于阈值则会先移除队头的key,再委托给组合进来的Cache来删除对应缓存操作,完成“先进先出”的增强(装饰)

LruCache

一个支持LRU(Least Recently Used ,最近最少使用)缓存策略的MyBatisCache

回忆下缓存策略

LRU:Least Recently Used,最近最少使用

LFU:Least Frequently Used,最近不常被使用

LRU 算法有一个缺点,比如说很久没有使用的一个键值,如果最近被访问了一次,那么即使它是使用次数最少的缓存,它也不会被淘汰;而 LFU 算法解决了偶尔被访问一次之后,数据就不会被淘汰的问题,它是根据总访问次数来淘汰数据的,其核心思想是“如果数据过去被访问多次,那么将来它被访问次数也会比较多”。因此 LFU 可以理解为比 LRU 更加合理的淘汰算法。

回忆下LinkedHashMap的核心机制-LRU

LinkedHashMap相比HashMap多了两个节点,before,after这样就能够维护节点之间的顺序了。

我们看看LinkedHashMap的get方法,它内部有LinkedHashMap开启LRU机制的秘密。

public V get(Object key) { Node<K,V> e; if ((e = getNode(hash(key), key)) == null) return null; if (accessOrder) // 为true则会执行afterNodeAccess(将节点移动到队尾) afterNodeAccess(e); return e.value; } 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; } }

那么这个accessOrder变量是怎么维护的呢?看代码

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

你会发现,LinkedHashMap有这么一个构造函数,第三个参数便是accessOrder,所以决定是否开启LRU是你在运行时传参决定的!开启后则会在每次读取键值对之后将读取的节点移动至队尾,那么队头就是最近最少使用的了,队尾就是刚刚使用的了,当需要删除最近最少使用的节点的时候,直接删除队头的即可。

回忆下LinkedHashMap的核心方法-removeEldestEntry

LinkedHashMap是一个有顺序的HashMap,它可以使得你的k,v能够按照某种顺序写入和读取,它的核心方法removeEldestEntry功不可没。

在HashMap新增k,v之后会回调一个方法“afterNodeInsertion”,这个方法在HashMap中是一个空实现(俗称钩子方法),它的子类LinkedHashMap重写了它,代码如下。

void afterNodeInsertion(boolean evict) { // possibly remove eldest 这是官方注释,言简意赅(可能会删除老key) LinkedHashMap.Entry<K,V> first; //前面的短路方法不管,我们关注removeEldestEntry方法 -> 如果该方法也返回true,则会走方法体中的removeNode方法(删除first节点的元素)。 // 当开启LinkedHashMap的LRU模式,则队头的元素是“最近最少使用的元素”,因为每次读取k,v后都会将元素调整至队尾,所以队头的元素是“最近最少使用的元素“ if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } } 进入正题 private final Cache delegate; // 维护一个key和value都是缓存key的map private Map<Object, Object> keyMap; //最近最少使用的Key private Object eldestKey; public LruCache(Cache delegate) { //通过构造函数,将Cache组合进来,取名”委托“ this.delegate = delegate; //初始化keyMap(重要) setSize(1024); } public void setSize(final int size) { // 构造函数第三个参数传递true(accessOrder),如上所述将开启LRU模式 keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) { private static final long serialVersionUID = 4267176411845948333L; // 重写了LinkedHashMap的方法 @Override protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) { boolean tooBig = size() > size; if (tooBig) { // 大小超过阈值,将队头(最近最少使用)的key更新至自身维护的"eldestKey" (重要) eldestKey = eldest.getKey(); } return tooBig; } }; } @Override public void putObject(Object key, Object value) { // 委托写入缓存 delegate.putObject(key, value); // 删除最近最少使用的缓存 cycleKeyList(key); } @Override public Object getObject(Object key) { keyMap.get(key); // touch return delegate.getObject(key); } @Override public Object removeObject(Object key) { return delegate.removeObject(key); } private void cycleKeyList(Object key) { // 因为重写了LinkedHashMap的removeEldestEntry方法,如上所述,超过阈值后eldestKey指向的就是最近最少使用的key keyMap.put(key, key); if (eldestKey != null) { // 委托移除最近最少使用的缓存 delegate.removeObject(eldestKey); // 置空 eldestKey = null; } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgdjjj.html