序文
LRUはLeast Recently Used
、速記文字通りています最近最少使用
。
一般的にキャッシュメモリが非常に貴重なものですので、達成するためにキャッシュ戦略を排除するために使用されるので、一定のルールに従って、メモリが占有されていないことを確認するために、データを除去する必要があります。
でredis
データが含まれていますフェーズアウト戦略LRU淘汰算法
どのように完全に達成するためにLRU缓
店にそれを?このキャッシュは、満たすために:
- このキャッシュは、順序を記録するために使用します
- キャッシュの使用の変更に伴い、キャッシュ順序が更新できるようにするには
この特性に基づいて、共通のデータ構造を使用することが可能です。链表
- 新しいキャッシュを追加するときは、必ずリストに追加
头节点
- キャッシュは再びキャッシュされたを移動するために使用された場合
头节点
- リストを削除することができますよりも、あなたは最大のキャッシュバッファを追加する場合
尾节点
の要素を
選択リストのシングルと双方向リンクリスト
方向の単一のリストのみ、ノードが存在するnext
後続ノードへのポインタ。二重にリンクされたリストは、二つの方向をサポートすることができる2つのポインタを持って、各ノードが複数の持っているnext
ポインタを、そこにあるpre
のVの先行ノードポインタ
二重リンクリストは、二つの先行ノードポインタ格納するために追加のスペースを必要prev
と後続ノードのポインタをnext
、データが同じサイズに格納されているように、より多くのスペースが二重連結リストを要求されます。比較方法が各ノードは、リスト、複数の空間に二重リンクリストポインタをリンクされたが、この構造は、このようなデータ構造のために非常に適したいくつかのシナリオでは、より多くの柔軟性をもたらします。削除し、ノードを追加、時間複雑さが二重にリンクされたリストのO(1)
片方向リンク・リストにおいて、削除ノードを追加する際の複雑さが既にO(1)、二重リンクリストは、単独でリンクされたリスト、それよりも効率的であることができますか?
のは、見てみましょう删除操作
:
削除操作での2つのケースがあります。
- ノード与えられた値を削除するには
- 与えられたノードポインタを削除するには
最初のケースでは、与えられた値を削除するいずれか、またはあなたが値を見つけるまでは、ポインタが削除されるので、トラバースリストの先頭から開始する必要があります
時間の複雑さを削除するには、この操作はOであるが、(1)が、主に削除されたノードトラバーサル時間を消費する、対応する時間複雑度はO(N)であるので、合計時間複雑度(n)はOです。
後者の場合、それはあなたが片方向リンクリストを使用している場合は、削除するノードを与えている、あなたはノードを削除する先行ノードが見つかるまで、リンクリストトラバーサルヘッドからスタートしなければなりませんでした。しかし、大きな利点である双方向リンクリスト、のために、ノードタイプを削除することが二重にリンクされたリストは、前駆体ノードが含まれ、削除操作はO(1)時間計算量を必要とします
同じ理由添加操作:
私たちは、指定されたノードのか、前後に要素を挿入したい場合は、双方向リンクリスト大きな利点があるが、彼は(1)時間の複雑さはOで得ることができますが、最初からも片方向リンクリストトラバーサル。
だから、二重にリンクされたリストは、単独リンクリストよりも多くのストレージスペースが、より広くの二重リンクリストを、必要とするが、JDKのLinkedHashMap二重リンクリストの使用上のデータ構造のこの種
LRUキャッシュを実現する方法
達成するために、単独リンクリスト
我々は、単一のリストに基づいて、簡単なコードの実装を与える下:
package com.ranger.lru;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author ranger
* LRU缓存
*
*/
public class LRUMap<K,V> {
/**
* 定义链表节点
* @author ranger
*
* @param <K>
* @param <V>
*/
private class Node<K, V> {
private K key;
private V value;
Node<K, V> next;
public Node(K key, V value) {
this.key = key;
this.value = value;
}
public Node() {
}
}
/**
* 缓存最大值
*/
private int capacity;
/**
* 当前缓存数量
*/
private int size;
/**
* 缓存链表头节点
*/
private Node<K,V> head;
/**
* 缓存链表尾节点
*/
private Node<K,V> tail;
/**
* 定义带参构造函数,构造一个为空的双向链表
* @param capacity 缓存最大容量
*/
public LRUMap(int capacity) {
this.capacity = capacity;
head = null;
tail = null;
size = 0;
}
/**
* 无参构造函数,初始化容量为16
*/
public LRUMap() {
this(16);
}
/**
* 向双向链表中添加节点
* @param key
* @param value
*/
public void put(K key,V value) {
addNode(key,value);
}
/**
* 根据key获取缓存中的Value
* @param key
* @return
*/
public V get(K key) {
Node<K,V> retNode = getNode(key);
if(retNode != null) {
// 存在,插入头部
moveToHead(retNode);
return retNode.value;
}
// 不存在
return null;
}
/**
* 移动给定的节点到头节点
* @param node
*/
public void moveToHead(Node<K,V> node) {
// 如果待移动节点是最后一个节点
if(node == tail) {
Node prev = head;
while(prev.next != null && prev.next != node) {
prev = prev.next;
}
tail = prev;
node.next = head;
head = node;
prev.next = null;
}else if(node == head){ // 如果是头节点
return;
}else {
Node prev = head;
while(prev.next != null && prev.next != node) {
prev = prev.next;
}
prev.next = node.next;
node.next = head;
head = node;
}
}
/**
* 获取给定key的节点
* @param key
* @return
*/
private Node<K,V> getNode(K key){
if(isEmpty()) {
throw new IllegalArgumentException("list is empty,cannot get node from it");
}
Node<K,V> cur = head;
while(cur != null) {
if(cur.key.equals(key)) {
return cur;
}
cur = cur.next;
}
return null;
}
/**
* 添加到头节点
* @param key
* @param value
*/
private void addNode(K key,V value) {
Node<K,V> node = new Node<>(key,value);
// 如果容量满了,删除最后一个节点
if(size == capacity) {
delTail();
}
addHead(node);
}
/**
* 删除最后一个节点
*/
private void delTail() {
if(isEmpty()) {
throw new IllegalArgumentException("list is empty,cannot del from it");
}
// 只有一个元素
if(tail == head) {
tail = null;
head = tail;
}else {
Node<K,V> prev = head;
while(prev.next != null && prev.next != tail) {
prev = prev.next;
}
prev.next = null;
tail = prev;
}
size--;
}
/**
* 链表是否为空
* @return
*/
private boolean isEmpty() {
return size == 0;
}
/**
* 添加节点到头头部
* @param node
*/
private void addHead(Node node) {
// 如果链表为空
if(head == null) {
head = node;
tail = head;
}else {
node.next = head;
head = node;
}
size ++;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Node<K,V> cur = head;
while(cur != null) {
sb.append(cur.key)
.append(":")
.append(cur.value);
if(cur.next != null) {
sb.append("->");
}
cur = cur.next;
}
return sb.toString();
}
/**
* 测试
* @param args
*/
public static void main(String[] args) {
LRUMap<String,String> lruMap = new LRUMap(3) ;
lruMap.put("1","tom") ;
lruMap.put("2","lisa") ;
lruMap.put("3","john") ;
System.out.println(lruMap.toString());
lruMap.put("4","july") ;
System.out.println(lruMap.toString());
lruMap.put("5","jack") ;
System.out.println(lruMap.toString());
String value = lruMap.get("3");
System.out.println(lruMap.toString());
System.out.println("the value is: "+value);
String value1 = lruMap.get("1");
System.out.println(value1);
System.out.println(lruMap.toString());
}
}
输出结果:
3:john->2:lisa->1:tom
4:july->3:john->2:lisa
5:jack->4:july->3:john
3:john->5:jack->4:july
the value is: john
null
3:john->5:jack->4:july
達成のLinkedHashMap
学ぶLinkedHashMap
こともあり、リストの認識に基づいているすべてのは知っている、 accessOrder
メンバ変数を、デフォルトである false
として、挿入ソートの順序に従ってデフォルト true
のアクセス順序によってソートされたとき、あなたはまた、コンストラクタの通過を呼び出すことができますaccessOrder
LinkedHashMap
ソートの2つの方法があります。
- ソート順序を記述します。
- ソートアクセス順序。
ソート順序アクセス、各前記get
値は、リストの末尾に移動する操作がアクセスをすることによりソートされたリストを順番に繰り返すことができるように、アクセスされます
私たちは、書き換えることができるノードを追加するための最も最近使用時にノードを削除するかどうかを決定する方法をLinkedHashMap
removeEldestEntry
コードは次のように実装されています。
public class LRULinkedHashMap<K,V> {
/**
* 缓存map
*/
private LinkedHashMap<K,V> cacheMap;
/**
* 当前缓存数量
*/
private int size;
/**
* 构造一个cacheMap,并设置可以缓存的数量
* @param size
*/
public LRULinkedHashMap(int size) {
this.size = size;
cacheMap = new LinkedHashMap<K,V>(16,0.75F,true) {
@Override
// 重写方法,判断是否删除最久没使用的节点
protected boolean removeEldestEntry(Map.Entry eldest) {
if (size + 1 == cacheMap.size()){
return true ;
}else {
return false ;
}
}
};
}
/**
* 添加缓存
* @param key
* @param value
*/
public void put(K key,V value){
cacheMap.put(key,value) ;
}
/**
* 获取缓存
* @param key
* @return
*/
public V get(K key){
return cacheMap.get(key) ;
}
public String toString() {
StringBuilder sb = new StringBuilder();
Set<Entry<K, V>> entrySet = cacheMap.entrySet();
for (Entry<K,V> entry : entrySet) {
sb.append(entry.getKey())
.append(":")
.append(entry.getValue())
.append("<-");
}
return sb.toString();
}
public static void main(String[] args) {
LRULinkedHashMap<String,Integer> map = new LRULinkedHashMap(3) ;
map.put("1",1);
map.put("2",2);
map.put("3",3);
System.out.println(map);
map.put("4", 4);
System.out.println(map);
}
}