缓存
缓存作用:优化性能,减少计算次数。减少数据库访问次数和压力。
缓存污染:将不常用的数据添加到缓存,降低缓存效率。
缓存淘汰:将不常用的缓存数据清理掉,避免内存溢出。
LRU(Least recently used)算法
根据数据历史访问记录淘汰缓存数据,
核心思想:“如果数据最近被访问过,那么将来被访问的几率也越高”。
一、实现思路:
最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:
- 添加新数据到链表头部。
- 每当缓存命中(即缓存数据被访问),则将数据移到链表头部。
- 当链表满时,丢弃链表尾部数据。
二、Java代码实现
为了快速查找缓存数据,本文示例使用哈希表存储缓存。
以下示例采用循环链表(包含头尾结点)和哈希表实现简单LRU算法。
import com.google.common.base.Objects;
import org.junit.Assert;
import java.util.HashMap;
import java.util.Map;
/**
* 简单实现LRU缓存淘汰算法(基于循环链表和哈希表实现缓存历史访问列表)
* 根据数据历史访问记录淘汰缓存数据(缓存命中率低,存在时间久的数据优先淘汰)
*
* @author genxinliu@hengtiansoft.com
* @see Node 链表结点 循环链表(包含头尾结点,使代码更简洁)
* @see HashMap 哈希表
* @since 03.12.2019
*/
public class SimpleLRUCache<K, V> {
static class Node<K, V> {
Node<K, V> prev, next;
final K key;
V val;
public Node() {
this.key = null;
}
public Node(K key, V val) {
this.key = key;
this.val = val;
}
public Node(K key, V val, Node<K, V> prev, Node<K, V> next) {
this.prev = prev;
this.next = next;
this.key = key;
this.val = val;
}
public K getKey() {
return key;
}
public V getVal() {
return val;
}
public void setVal(V val) {
this.val = val;
}
public Node<K, V> getPrev() {
return prev;
}
public void setPrev(Node<K, V> node) {
this.prev = node;
}
public Node<K, V> getNext() {
return next;
}
public void setNext(Node<K, V> node) {
this.next = node;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Node<?, ?> node = (Node<?, ?>) o;
return Objects.equal(key, node.key);
}
@Override
public int hashCode() {
return Objects.hashCode(key);
}
@Override
public String toString() {
return "Node{" +
"key=" + key +
'}';
}
}
/** 链表头结点 */
private final Node<K, V> head = new Node<K, V>();
/** 链表尾结点 */
private final Node<K, V> tail = new Node<K, V>();
/** 链表长度 */
private int limit;
private Map<K, Node<K, V>> cache;
public SimpleLRUCache(int limit) {
this.limit = limit;
cache = new HashMap<K, Node<K, V>>(limit);
// 关联头尾结点
head.prev = head.next = tail;
tail.prev = tail.next = head;
}
public static void main(String[] args) {
SimpleLRUCache<String, String> cache = new SimpleLRUCache<>(3);
cache.put("1", "1");
cache.put("2", "2");
cache.put("3", "3");
cache.put("4", "4");
Assert.assertTrue("1 is not out!!!!", cache.get("1") == null);
cache.get("2");
cache.put("5", "5");
Assert.assertTrue("2 is out!!!!", cache.get("2") != null);
}
/**
* 新增缓存数据到哈希表和链表头部
* 如果缓存已存在,则更新缓存数据
* 如果已达到链表上限,则淘汰链表尾端的数据 再做新增处理
*
* @param key 缓存的key
* @param val 缓存数据
*/
public void put(K key, V val) {
synchronized (cache) {
if (cache.containsKey(key)) {
// refresh val
cache.get(key).setVal(val);
} else {
if (cache.size() >= limit) {
// remove tail
Node<K, V> tail = tail();
nodeRemoval(tail);
cache.remove(tail.getKey());
}
Node<K, V> node = new Node<>(key, val);
// add first
linkNodeFirst(node);
// add cache
cache.put(key, node);
}
}
}
/**
* 获取缓存,如果命中缓存,则将数据添加到链表头部
*
* @param key
*/
public V get(K key) {
Node<K, V> crt = cache.get(key);
if (crt != null) {
// double check locking
if(head() != crt) {
synchronized (head) {
if (head() != crt) {
nodeRemoval(crt);
linkNodeFirst(crt);
}
}
}
return crt.val;
}
return null;
}
// add dst before head.next
private void linkNodeFirst(Node<K, V> dst) {
linkNodeBefore(head.next, dst);
}
// add dst before tail
private void linkNodeLast(Node<K, V> dst) {
linkNodeBefore(tail, dst);
}
// add dst before src
private void linkNodeBefore(Node<K, V> src, Node<K, V> dst) {
Node<K, V> p = src.prev;
p.next = dst;
dst.prev = p;
dst.next = src;
src.prev = dst;
}
// dst replace src
private void transferLinks(Node<K, V> src, Node<K, V> dst) {
Node<K, V> p = src.prev, n = src.next;
src.prev = src.next = null;
p.next = dst;
dst.prev = p;
dst.next = n;
n.prev = dst;
}
// remove dst
private void nodeRemoval(Node<K, V> dst) {
if (null != dst) {
Node<K, V> p = dst.prev, n = dst.next;
dst.prev = dst.next = null;
p.next = n;
n.prev = p;
}
}
// return head.next. if is tail return null
private Node<K, V> head() {
return head.next == tail ? null : head.next;
}
// return tail.prev. if is head return null
private Node<K, V> tail() {
return tail.prev == head ? null : tail.prev;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。