LRU缓存实现

MyBatis实现LRU缓存
package org.apache.ibatis.cache.decorators;

import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.ibatis.cache.Cache;

/**
 * Lru (least recently used) cache decorator.
 *
 * @author Clinton Begin
 */
public class LruCache implements Cache {

	private final Cache delegate;
	private Map<Object, Object> keyMap;
	private Object eldestKey;

	public LruCache(Cache delegate) {
		this.delegate = delegate;
		// 默认缓存大小为1024
		setSize(1024);
	}

	@Override
	public String getId() {
		return delegate.getId();
	}

	@Override
	public int getSize() {
		return delegate.getSize();
	}

	public void setSize(final int size) {
		// 通过LinkedHashMap实现LRU缓存
		// 此处 accessOrder = true 代表排序规则为以访问顺序排列
		// the ordering mode - true for access-order, false for insertion-order
		keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
			private static final long serialVersionUID = 4267176411845948333L;

			@Override
			protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
				boolean tooBig = size() > size;
				if (tooBig) {
					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);
	}

	@Override
	public void clear() {
		delegate.clear();
		keyMap.clear();
	}

	private void cycleKeyList(Object key) {
		keyMap.put(key, key);
		if (eldestKey != null) {
			delegate.removeObject(eldestKey);
			eldestKey = null;
		}
	}
}
LinkedHashMap简介

Hash table and linked list implementation of the Map interface, with predictable iteration order. This implementation differs from HashMap in that it maintains a doubly-linked list running through all of its entries. This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order).
从上面这段可知LinkedHashMap其实是HashMap与LindedList的综合版本,可预测的迭代顺序是由链表来保证的,这也是不同于HashMap的地方,内部维护了一个双向链表用于链接每一个Map的Entry。默认情况下,这个链表是FIFO的,通过参数,可以变为LRU(也就是每个被最新使用的元素会被移动到列表最前端)

Note that insertion order is not affected if a key is re-inserted into the map. (A key k is reinserted into a map m if m.put(k, v) is invoked when m.containsKey(k) would return true immediately prior to the invocation.)
如果一个key已经存在于map中,再次插入不会影响原来的插入顺序。
因为当已经存在于map的key再次被插入时,在判断map是否包含key时会直接返回true

This implementation spares its clients from the unspecified, generally chaotic ordering provided by HashMap (and Hashtable), without incurring the increased cost associated with TreeMap. It can be used to produce a copy of a map that has the same order as the original, regardless of the original map’s implementation:
void foo(Map m) {
Map copy = new LinkedHashMap(m);

}
这个类实现与HashMap和Hashtable相比能保证顺序性,而语TreeMap比较更有效率。另外这个类可以通过传入其他Map构造实例,顺序与传入的Map一致,不管原来的Map是什么样的实现。
This technique is particularly useful if a module takes a map on input, copies it, and later returns results whose order is determined by that of the copy. (Clients generally appreciate having things returned in the same order they were presented.)

A special constructor is provided to create a linked hash map whose order of iteration is the order in which its entries were last accessed, from least-recently accessed to most-recently (access-order). This kind of map is well-suited to building LRU caches. Invoking the put, putIfAbsent, get, getOrDefault, compute, computeIfAbsent, computeIfPresent, or merge methods results in an access to the corresponding entry (assuming it exists after the invocation completes). The replace methods only result in an access of the entry if the value is replaced. The putAll method generates one entry access for each mapping in the specified map, in the order that key-value mappings are provided by the specified map’s entry set iterator. No other methods generate entry accesses.
// 对于容器视图的访问并不影响排序 比如keySet或values
In particular, operations on collection-views do not affect the order of iteration of the backing map.

The removeEldestEntry(Map.Entry) method may be overridden to impose a policy for removing stale(不新鲜的) mappings automatically when new mappings are added to the map.

允许空元素 性能比hashMap差一些(维护链表)但是遍历视图会更快 与map的大小成比例 而HashMap的视图遍历与容量成比例
This class provides all of the optional Map operations, and permits null elements. Like HashMap, it provides constant-time performance for the basic operations (add, contains and remove), assuming the hash function disperses elements properly among the buckets. Performance is likely to be just slightly below that of HashMap, due to the added expense of maintaining the linked list, with one exception: Iteration over the collection-views of a LinkedHashMap requires time proportional to the size of the map, regardless of its capacity. Iteration over a HashMap is likely to be more expensive, requiring time proportional to its capacity.

初始大小和加载因子是主要影响性能的因子
A linked hash map has two parameters that affect its performance: initial capacity and load factor. They are defined precisely as for HashMap.
但是请注意,与HashMap相比,此类为初始容量选择过高的值的惩罚不那么严重,因为此类的迭代时间不受容量的影响
Note, however, that the penalty for choosing an excessively high value for initial capacity is less severe for this class than for HashMap, as iteration times for this class are unaffected by capacity.

Note that this implementation is not synchronized. If multiple threads access a linked hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. This is typically accomplished by synchronizing on some object that naturally encapsulates the map. If no such object exists, the map should be “wrapped” using the Collections.synchronizedMap method. This is best done at creation time, to prevent accidental unsynchronized access to the map:
Map m = Collections.synchronizedMap(new LinkedHashMap(…));
// 影响迭代排序 --》 结构修改 在插入排序模式下,修改一个元素不属于结构修改 而在访问排序模式下 访问一个元素都属于结构修改
A structural modification is any operation that adds or deletes one or more mappings or, in the case of access-ordered linked hash maps, affects iteration order. In insertion-ordered linked hash maps, merely changing the value associated with a key that is already contained in the map is not a structural modification. In access-ordered linked hash maps, merely querying the map with get is a structural modification. )

The iterators returned by the iterator method of the collections returned by all of this class’s collection view methods are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator’s own remove method, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

// 快速失败
Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

The spliterators returned by the spliterator method of the collections returned by all of this class’s collection view methods are late-binding, fail-fast, and additionally report Spliterator.ORDERED.

This class is a member of the Java Collections Framework.

实现原理
/*
 * 继承自HashMap 在新增元素或删除元素时有回调方法
 * Implementation note.  A previous version of this class was
 * internally structured a little differently. Because superclass
 * HashMap now uses trees for some of its nodes, class
 * LinkedHashMap.Entry is now treated as intermediary(中间的) node class
 * that can also be converted to tree form. The name of this
 * class, LinkedHashMap.Entry, is confusing in several ways in its
 * current context, but cannot be changed.  Otherwise, even though
 * it is not exported outside this package, some existing source
 * code is known to have relied on a symbol resolution corner case
 * rule in calls to removeEldestEntry that suppressed compilation
 * errors due to ambiguous usages. So, we keep the name to
 * preserve unmodified compilability.
 *
 * The changes in node classes also require using two fields
 * (head, tail) rather than a pointer to a header node to maintain
 * the doubly-linked before/after list. This class also
 * previously used a different style of callback methods upon
 * access, insertion, and removal.
 */
源码解析
  1. 回调方法 其中afterNodeAccess和afterNodeInsertion与实现LRU缓存有关
    // Callbacks to allow LinkedHashMap post-actions 来自于HashMap
    
    // HashMap -> compute、merge、computeIfAbsent 
	// LinkedHashMap -> get -> afterNodeAccess
    void afterNodeAccess(Node<K,V> p) { }
	
    // HashMap -> compute、merge、computeIfAbsent 
	// computeIfAbsent
    // HashMap -> put -> putVal
    void afterNodeInsertion(boolean evict) { }
    
    // HashMap -> compute、merge 
	// HashMap remove -> removeNode -> afterNodeRemoval
    void afterNodeRemoval(Node<K,V> p) { }
  1. 相应方法以及回调实现


	/**
     * The head (eldest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> head;

    /**
     * The tail (youngest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> tail;

    /**
     * The iteration ordering method for this linked hash map: <tt>true</tt>
     * for access-order, <tt>false</tt> for insertion-order.
     *
     * @serial
     */
    final boolean accessOrder;

    /**
     * HashMap.Node subclass for normal LinkedHashMap entries.
     */
    static class Entry<K,V> extends HashMap.Node<K,V> {
        // 通过before和after来维持一个双向链表
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }


   	public V get(Object key) {
        Node<K,V> e;
        // 调用父类的获取元素的方法
        if ((e = getNode(hash(key), key)) == null)
            return null;
        // accessOrder = true access-order , otherwise insertion-order
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

    // 将当前访问的元素移到链表的最后
    // 在get方法和父类put方法中回调
    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        // 此处再次判断accessOrder
        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;
        }
    }

   // 父类put方法中的回调  evict 逐出的意思
   // evict false when initially constructing this map,  初始化或者反序列化不会逐出 
   // else true (relayed to method afterNodeInsertion.
   void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

    void afterNodeRemoval(Node<K,V> e) { // unlink
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.before = p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a == null)
            tail = b;
        else
            a.before = b;
    }
结论
  1. LinkedHashMap继承自HashMap,键值对仍然通过节点表示,但是节点之间通过before和after维持了双向链表的关系,因此在遍历访问时比HashMap性能要好,一个是基于size大小,一个是基于capacity容量。
  2. 通过设置accessOrder的值,可以实现FIFO缓存(accessOrder = false 默认值)和LRU缓存(accessOrder = true)。主要是因为LinkedHashMap内部的双向链表,以及在put、get等操作时会进行afterNodeInsertion和afterNodeAccess方法的回调,比如accessOrder为true时,afterNodeAccess方法会将访问的元素移到链表的尾部,而evict节点则从头节点开始
  3. FIFO缓存和LRU缓存归根结底还是基于双向链表来实现的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值