LRU Cache java实现

java

要求:

  • get(key):如果key在cache中,则返回对应的value值,否则返回null
  • set(key,value):如果key不在cache中,则将该(key,value)插入cache中(注意,如果cache已满,则必须把最近最久未使用的元素从cache中删除);如果key在cache中,则重置value的值。
  • set和get的时间复杂度都是O(1)。

两个map

/**

* 思路:时间复杂度是O(1),一下子想到的是map。但是怎么进行淘汰呢?需要记录时间,且查找的复杂度也是O(1),那也用map吧。

* put之前,需要先查看是否包含key,如果包含,删掉之前的数据,再放进新的数据,且把key的访问时间更新。淘汰最早访问数据的时候,还需要根据访问时间查询key。

* 所以,需要的操作包括:根据key获取value和access;根据access获取key。

*/

public class MyLRUMapSample<K, V> {

//cache的容量

private int capacity;

//存储key-value/access的map

private HashMap<K, Pair<V>> valueMap;

//存储access-key的map,使用SortedMap,对access进行排序

private SortedMap<Long, K> accessKeyMap;

public MyLRUMapSample(){}

public MyLRUMapSample(int capacity){

this.capacity = capacity;

valueMap = new HashMap<K, Pair<V>>();

accessKeyMap = new TreeMap<Long, K>();

}

public V get(K key){

if(!valueMap.containsKey(key)){

return null;

}

//修改access

Long access = accessKeyMap.lastKey() + 1;

accessKeyMap.remove(valueMap.get(key).access);

accessKeyMap.put(access, key);

valueMap.get(key).access = access;

return valueMap.get(key).value;

}

public void put(K key, V value){

//如果含有值,则更新

if(valueMap.containsKey(key)){

Long oldAccess = valueMap.get(key).access;

accessKeyMap.remove(oldAccess);

valueMap.remove(key);

}

//如果存储已满,则淘汰掉最早访问的

if(valueMap.size() >= capacity){

Long oldAccess = accessKeyMap.firstKey();

valueMap.remove(accessKeyMap.get(oldAccess));

accessKeyMap.remove(oldAccess);

}

//添加最新的数据

Long access = accessKeyMap.isEmpty() ? 0 : accessKeyMap.lastKey() + 1;

valueMap.put(key, new Pair(access, value));

accessKeyMap.put(access, key);

}

//访问时间和value值

class Pair<V>{

public Long access;

public V value;

public Pair(){}

public Pair(Long access, V value){

this.access = access;

this.value = value;

}

}

public K getOldestKey(){

return accessKeyMap.isEmpty() ? null : accessKeyMap.get(accessKeyMap.firstKey());

}

public K getLatestKey(){

return accessKeyMap.isEmpty() ? null : accessKeyMap.get(accessKeyMap.lastKey());

}

public static void main(String[] args) {

LinkedListMapLRUSample<Integer, Integer> sample = new LinkedListMapLRUSample<Integer, Integer>(2);

Assert.check(sample.getLatestKey() == null);

Assert.check(sample.get(2) == null);

sample.put(2, 1);

sample.put(2, 2);

Assert.check(sample.get(2).intValue() == 2);

sample.put(1, 2);

sample.put(1, 3);

Assert.check(sample.get(1) == 3);

Assert.check(sample.get(2).intValue() == 2);

sample.put(3, 3);

Assert.check(sample.get(1) == null);

}

}

双向链表+hashMap

/**

* 使用LinkedList存储数据,从头部插入,从尾部淘汰。这样就保证了容量和淘汰规则正确性。

* 使用hashMap,通过key找到value。

*/

public class LinkedListMapLRUSample<K,V> {

class Node<K, V>{

public K key;

public V value;

public Node preNode;

public Node nextNode;

public Node(){}

public Node(K key, V value, Node preNode, Node nextNode){

this.key = key;

this.value = value;

this.preNode = preNode;

this.nextNode = nextNode;

}

}

private Node<K,V> head;

private Node<K,V> tail;

private HashMap<K,Node<K, V>> valueMap;

private int capacity;

public LinkedListMapLRUSample(){}

public LinkedListMapLRUSample(int capacity){

this.capacity = capacity;

valueMap = new HashMap<K,Node<K, V>>();

}

public void put(K key, V value){

if(head == null){

head = new Node(key, value, null, null);

tail = head;

valueMap.put(key, head);

return;

}

//如果已经包含了数据,需要更新

if(valueMap.containsKey(key)){

valueMap.get(key).value = value;

moveToHead(key);

return;

}

//满容量,需要淘汰掉最旧的数据

if(valueMap.size() >= capacity){

valueMap.remove(tail.key);

tail = tail.preNode;

if(tail != null){

tail.nextNode = null;

}

}

insertAsHead(key, value);

}

private void insertAsHead(K key, V value){

//在头部添加新节点

Node target = new Node(key, value, null, head);

if(head != null){

head.preNode = target;

target.nextNode = head;

}

valueMap.put(key, target);

head = target;

}

private void moveToHead(K key){

Node target = valueMap.get(key);

//头部元素

if(target.preNode == null){

return;

}

//尾部元素

if(target.nextNode == null){

tail = target.preNode;

tail.nextNode = null;

}else{

target.preNode.nextNode = target.nextNode;

target.nextNode.preNode = target.preNode;

}

target.preNode = null;

target.nextNode = head;

head.preNode = target;

head = target;

}

public V get(K key){

if(!valueMap.containsKey(key)){

return null;

}

moveToHead(key);

return valueMap.get(key).value;

}

public K getLatestKey(){

return head == null ? null : head.key;

}

public K getOldestKey(){

return tail == null ? null : tail.key;

}

public static void main(String[] args) {

LinkedListMapLRUSample<Integer, Integer> sample = new LinkedListMapLRUSample<Integer, Integer>(2);

Assert.check(sample.getLatestKey() == null);

Assert.check(sample.get(2) == null);

sample.put(2,1);

sample.put(2, 2);

Assert.check(sample.get(2).intValue() == 2);

sample.put(1, 2);

sample.put(1, 3);

Assert.check(sample.get(1) == 3);

Assert.check(sample.get(2).intValue() == 2);

sample.put(3, 3);

Assert.check(sample.get(1) == null);

}

}

需要根据访问时间或者插入时间进行排序时,考虑使用双向链表。

最先插入淘汰和最少访问淘汰

  • 最先插入淘汰,即FIFO。也就是要求set操作时,新数据放在head,get操作不需要移动。那么,只需要在get操作的时候直接返回valueMap中的数据即可。
  • 最少访问淘汰,是按照访问次数排序,将访问次数最少的数据删除。

    可以使用两个map实现,valueMap(HashMap)和accessCountMap(SortedMap)。accessCount初始为1,get/put时,accessCount自增,并按照accessCount排序。

如果用数组和map实现,就需要accessCount自增后,进行重排序;或者在需要淘汰的时候进行重排序。

LinkedHashMap源码解读

LinkedHashMap使用一个双向链表加一个HashMap。同时有一个成员变量accessOrder控制淘汰策略:为true 表示按照LRU方式淘汰,false表示按照FIFO方式淘汰。

另外还有一个方法控制是否删除最旧的数据:

    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {

return false;

}

//put新数据的时候,检查是否需要删除最旧的数据

void addEntry(int hash, K key, V value, int bucketIndex) {

super.addEntry(hash, key, value, bucketIndex);

// Remove eldest entry if instructed

Entry<K,V> eldest = header.after;

if (removeEldestEntry(eldest)) {

removeEntryForKey(eldest.key);

}

}

使用LinkedHashMap实现LRUCache:

public class LinkedHashMapLRUSample<K, V> extends LinkedHashMap<K, V>{

private int capacity;

public LinkedHashMapLRUSample(){}

public LinkedHashMapLRUSample(int capacity){

super(16, 0.75f, true);

this.capacity = capacity;

}

@Override

protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {

return this.size() > capacity;

}

public static void main(String[] args) {

LinkedHashMapLRUSample<Integer, Integer> sample = new LinkedHashMapLRUSample<Integer, Integer>(2);

Assert.check(sample.get(2) == null);

sample.put(2,1);

sample.put(2, 2);

Assert.check(sample.get(2).intValue() == 2);

sample.put(1, 3);

Assert.check(sample.get(1) == 3);

Assert.check(sample.get(2).intValue() == 2);

sample.put(3, 3);

Assert.check(sample.get(1) == null);

}

}

参考

缓存算法和实现 : 对各种缓存算法做了说明。
LRU算法机器变种

以上是 LRU Cache java实现 的全部内容, 来源链接: utcz.com/z/392110.html

回到顶部