JAVA实现LRU缓存淘汰

1、缓存淘汰算法

      经常使用的缓存淘汰算法有FIFO,LRU,LFU;最经常使用的当属于LRU,好比分布式缓存服务memcached其默认的缓存淘汰算法也是LRU;LRU的意思是淘汰最近最少使用的数据,本篇主要采用LinkedHashMap和双向链表来分别实现。java

2、LinkedHashMap实现

     LinkedHashMap用来实现LRU很是简单,LinkedHashmap若是超过了最大容量,会将链表头部的数据移除掉,而每次get访问的时候,访问的数据就会被移动到链表头部。node

    先看一段代码算法

   

package com.chongqin.java;

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

public class LinkedHashMap实现LRU<K, V> extends LinkedHashMap<K, V> {
	private static final long serialVersionUID = 1L;
	private int capacity;

	LinkedHashMap实现LRU(int capacity) {
		/**第三个参数设置为true,表示基于访问的顺序,get一个元素后,这个元素就会被放到链表尾部 */
		super(16, 0.75f, true);
		this.capacity = capacity;
	}

	/**
	 * linkedHashmap若是超过了最大容量,会将链表头部的数据移除掉,而每次get访问的时候,访问的数据就会被
	 * 移动到链表底部;
	 */
	@Override
	protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
		return size() > capacity;
	}

	public static void main(String[] args) {
	    Map<Integer,Integer> map=new LinkedHashMap实现LRU<>(4);  
	    map.put(9,3);  
	    map.put(7,4);  
	    map.put(5,9);  
	    map.put(3,4);  
	    map.put(6,6);  
	    //总共put了5个元素,超过了指定的缓存最大容量 (链表顶端的9会被remove调)
           for(Iterator<Map.Entry<Integer,Integer>> it=map.entrySet().iterator();it.hasNext();){  
               System.out.println(it.next().getKey());  
            }  
	}
}
   运行效果

  

7
5
3
6
 

3、双向链表实现


    对于链表的数据结构这里不说明,比较简单,直接上代码缓存

  

package com..java;

import java.util.Hashtable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class LRUCacheLinkHashMap<K, V> {

	private LinkCacheNode firstNode;

	private LinkCacheNode lastNode;

	private final int MAX_CACHE_SIZE;

	private Hashtable<K, LinkCacheNode> hashtable;

	public LRUCacheLinkHashMap(int cacheSize) {
		this.MAX_CACHE_SIZE = cacheSize;
		hashtable = new Hashtable<K, LinkCacheNode>(cacheSize);
	}

	/**
	 * 内部类,定义节点对象
	 * 
	 * @author tanjie
	 *
	 */
	class LinkCacheNode {
		// 前一个节点
		private LinkCacheNode prev;
		// 下一个节点
		private LinkCacheNode next;
		// 当前节点的key
		private K key;
		// 当前节点的value
		private V value;
	}

	// 当往链表里面放数据时,将最新的数据指向链表头
	public synchronized void put(K key, V value) {
		// 若是此时链表已经超过了最大的大小,则首先移除链表尾部的数据
		LinkCacheNode cacheNode = getCacheNode(key);
		if (null == cacheNode) {
			// 若是链表已经不容许放数据
			if (hashtable.size() >= MAX_CACHE_SIZE) {
				if (null != lastNode) {
					hashtable.remove(lastNode.key);
					// 当缓存容量满的时候,移除链表尾部的数据
					removeLastNode();
				}
			}
			cacheNode = new LinkCacheNode();
		}
		cacheNode.key = key;
		// 若是链接里面有值,直接修改value值便可,key不变
		cacheNode.value = value;
		// 新加入的移到链表头部
		moveToHeadNode(cacheNode);
		hashtable.put(key, cacheNode);
		System.out.println("当前线程:" + Thread.currentThread().getName()  + ",put操做:firstNode:" + firstNode);
		System.out.println("当前线程:" + Thread.currentThread().getName() +",put操做:lastNode:" + lastNode);
		System.out.println("=================================================");
	}

	/**
	 * 节点的获取,模拟每次获取一次,都将该节点移动到链表头,表示最近刚被访问过
	 * 
	 * @param key
	 * @return
	 */
	public synchronized Object get(K key) {
		LinkCacheNode node = getCacheNode(key);
		if (null == node) {
			return null;
		}
		// 每次key被请求一次,就移动到链表头,模拟当前数据刚被访问过
		moveToHeadNode(node);
		System.out.println("当前线程:" + Thread.currentThread().getName() + ",get操做:firstNode:" + firstNode);
		System.out.println("当前线程:" + Thread.currentThread().getName() + ",get操做:lastNode:" + lastNode);
		System.out.println("================================");
		return node.value;
	}

	/**
	 * 节点的删除
	 * 
	 * @param key
	 */
	public synchronized void remove(K key) {
		LinkCacheNode entry = getCacheNode(key);
		if (null != entry) {
			// 设置前一个节点的引用
			if (null != entry.prev) {
				entry.prev.next = entry.next;
			}
			// 反向设置下一个节点的引用
			if (null != entry.next) {
				entry.next.prev = entry.prev;
			}
			// 若是删除的是链表头部节点,将下一个节点设置为第一个节点
			if (entry == firstNode) {
				firstNode = entry.next;
			}
			// 若是删除的是链表尾部节点,将前一个节点设置为最后一个节点
			if (entry == lastNode) {
				lastNode = entry.prev;
			}
		}
		hashtable.remove(key);
	}

	/**
	 * 将节点移动到链表头
	 * @param cacheEntry
	 */
	private void moveToHeadNode(LinkCacheNode cacheEntry) {
		// 若是当前须要移动的就是第一个位置,则不须要移动
		if (cacheEntry == firstNode) {
			return;
		}
		// 在移动节点以前,先设置当前节点的先后节点的引用
		if (null != cacheEntry.prev) {
			cacheEntry.prev.next = cacheEntry.next;
		}
		if (null != cacheEntry.next) {
			cacheEntry.next.prev = cacheEntry.prev;
		}
		// 若是是最后一个节点移动到头部,将节点的前一个设置为尾部节点
		if (cacheEntry == lastNode) {
			lastNode = cacheEntry.prev;
		}
		if (null != firstNode) {
			// 移动节点的下一个节点的引用指向未移动前的链表的第一个节点
			cacheEntry.next = firstNode;
			// 未移动前链表的第一个节点的前一个节点的引用指向当前移动的节点
			firstNode.prev = cacheEntry;
		}
		// 将移动节点设置为链表头部节点
		firstNode = cacheEntry;
		cacheEntry.prev = null;
		if (null == lastNode) {
			lastNode = firstNode;
		}

	}

	/**
	 * 移除链表尾的数据
	 */
	private void removeLastNode() {
		if (null != lastNode) {
			if (null != lastNode.prev) {
				lastNode.prev.next = null;
				// 只有一个节点的时候
			} else {
				firstNode = null;
			}
			lastNode = lastNode.prev;
		}
	}

	/**
	 * 经过key获取节点数据对象
	 * 
	 * @param key
	 * @return
	 */
	private LinkCacheNode getCacheNode(K key) {
		return hashtable.get(key);
	}

	public static void main(String[] args) throws InterruptedException {
		final LRUCacheLinkHashMap<String, String> lru = 
				new LRUCacheLinkHashMap<String, String>(3);
		final CountDownLatch latch = new CountDownLatch(15);
		ExecutorService service = Executors.newCachedThreadPool();
		for(int i=1;i<=15;i++){
			final int index = i;
			service.submit(new  Runnable() {
				@Override
				public void run() {
					lru.put(String.valueOf(index), "value:" + index);
					latch.countDown();
				}
			});
		}
		latch.await();  
		service.shutdown();
	}
}

    运行效果

   

当前线程:pool-1-thread-1,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@3a9ea3d2
当前线程:pool-1-thread-1,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@3a9ea3d2
=================================================
当前线程:pool-1-thread-15,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@3282754
当前线程:pool-1-thread-15,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@3a9ea3d2
=================================================
当前线程:pool-1-thread-14,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@118c9d70
当前线程:pool-1-thread-14,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@3a9ea3d2
=================================================
当前线程:pool-1-thread-12,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@44e2d7cc
当前线程:pool-1-thread-12,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@3282754
=================================================
当前线程:pool-1-thread-13,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@39b79290
当前线程:pool-1-thread-13,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@118c9d70
=================================================
当前线程:pool-1-thread-9,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@2e509126
当前线程:pool-1-thread-9,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@44e2d7cc
=================================================
当前线程:pool-1-thread-11,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@7062106f
当前线程:pool-1-thread-11,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@39b79290
=================================================
当前线程:pool-1-thread-5,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@7833532f
当前线程:pool-1-thread-5,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@2e509126
=================================================
当前线程:pool-1-thread-7,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@6189f755
当前线程:pool-1-thread-7,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@7062106f
=================================================
当前线程:pool-1-thread-10,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@24c394d2
当前线程:pool-1-thread-10,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@7833532f
=================================================
当前线程:pool-1-thread-6,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@65d48e6a
当前线程:pool-1-thread-6,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@6189f755
=================================================
当前线程:pool-1-thread-8,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@7153e174
当前线程:pool-1-thread-8,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@24c394d2
=================================================
当前线程:pool-1-thread-4,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@4225e341
当前线程:pool-1-thread-4,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@65d48e6a
=================================================
当前线程:pool-1-thread-3,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@3ff961b5
当前线程:pool-1-thread-3,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@7153e174
=================================================
当前线程:pool-1-thread-2,put操做:firstNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@12b4445e
当前线程:pool-1-thread-2,put操做:lastNode:com.chongqin.java.LRUCacheLinkHashMap$LinkCacheNode@4225e341
=================================================