场景:请使用Java 8实现缓存服务。
该服务可以提供中等级别的缓存同时访问。
要实施的访问操作包括:
1. get(key) - 此操作将获取列表中键的值。如果密钥不存在,则返回-1。
2. set(key,value) - 如果密钥不存在,此操作将在列表中插入新的键/值,否则不执行任何操作。
3.缓存的大小可通过属性文件进行配置
要求:1.实现具有最近最少使用(LRU)策略的通用基于内存的缓存作为列表
2.实现访问操作的并发性。
主要代码实现:
package dpnice.cache;
import dpnice.common.GlobalConstants;
import dpnice.util.ConfigurationUtils;
import org.apache.log4j.Logger;
import java.util.concurrent.*;
/**
* 维护两种淘汰策略(FIFO LRU)的高速缓存
*
* @author DPn!ce date 2018 07 12 上午 10:19
*/
public class FastCache<K, V> {
private final static Logger log = Logger.getLogger(FastCache.class);
/**
* 最大缓存大小
* 单位 byte
*/
private Long cacheSize;
/**
* 初始化内存大小
*/
private Long initCacheSize;
/**
* FIFO并发缓存容器
*/
private ConcurrentHashMap<Object, CacheNode> nodesFIFO;
/**
* FIFO并发缓存容器大小
*/
private int nodesFIFOCacheSize = 10000;
/**
* FIFO链表头
*/
private CacheNode firstFIFO;
/**
* FIFO链表尾
*/
private CacheNode lastFIFO;
/**
* LRU并发缓存容器
*/
private ConcurrentHashMap<Object, CacheNode> nodesLRU;
/**
* LRU链表头
*/
private CacheNode firstLRU;
/**
* LRU链表尾
*/
private CacheNode lastLRU;
public FastCache() {
setCacheSize();
initCacheSize = getCurrentSize();
nodesLRU = new ConcurrentHashMap<>();
nodesFIFO = new ConcurrentHashMap<>(nodesFIFOCacheSize);
}
/**
* 添加缓存
*
* @param key 键
* @param value 对应值
*/
public synchronized void put(K key, V value) {
CacheNode cacheNode = null;
try {
cacheNode = getCacheNode(key);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
if (cacheNode == null) {
//FIFO缓存容器是否已经超过大小
if (nodesFIFO.size() > nodesFIFOCacheSize) {
if (firstFIFO != null) {
//先进先出
nodesFIFO.remove(firstFIFO.getKey());
firstFIFO.getNext().setFront(null);
firstFIFO = firstFIFO.getNext();
}
}
cacheNode = new CacheNode();
cacheNode.setKey(key);
cacheNode.setValue(value);
nodesFIFO.put(key, cacheNode);
if (lastFIFO != null) {
lastFIFO.setNext(cacheNode);
cacheNode.setFront(lastFIFO);
}
lastFIFO = cacheNode;
if (firstFIFO == null) {
firstFIFO = lastFIFO;
}
}
//不等于空说明找到node 不进行操作
}
/**
* 获取缓存中对象
*
* @param key 键
* @return 对应Object
*/
public Object get(K key) {
CacheNode cacheNode = null;
try {
cacheNode = getCacheNode(key);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
return cacheNode == null ? -1 : cacheNode.getValue();
}
/**
* 获取缓存中对象
*
* @param key 键
* @return 对应CacheNode
*/
private CacheNode getCacheNode(K key) throws ExecutionException, InterruptedException {
CompletableFuture<CacheNode> cacheNodeCompletableFuture = CompletableFuture.supplyAsync(
() -> {
if (firstLRU != null && firstLRU.getKey() == key) {
return firstLRU;
//本身就在第一个 不需要做移动
} else {
CacheNode node = nodesLRU.get(key);
if (node != null) {
//移动到第一个
moveToHead(node);
return node;
} else {
CacheNode node1 = nodesFIFO.get(key);
if (node1 != null) {
//从fifo队列删除 及fifo容器中删除
removeFIFO(node1);
//并添加到lru顶部 及lru链表顶部
putNodesLRU(node1);
return node1;
} else {
return null;
}
}
}
});
return cacheNodeCompletableFuture.get();
}
/**
* 表示这个节点最近被使用 -> 移动到链表头
*
* @param nodeLRU 存放在LRU缓存容器中的node
*/
private void moveToHead(CacheNode nodeLRU) {
if (nodeLRU.getFront() != null) {
//将nodeLRU的前一个的下一个指向nodeLRU的下一个
nodeLRU.getFront().setNext(nodeLRU.getNext());
}
if (nodeLRU.getNext() != null) {
//将nodeLRU的下一个的前一个指向nodeLRU前一个
nodeLRU.getNext().setFront(nodeLRU.getFront());
}
if (lastLRU == nodeLRU) {
//将尾部替换为lastLRU的上一个
lastLRU = nodeLRU.getFront();
}
if (firstLRU != null) {
nodeLRU.setNext(firstLRU);
firstLRU.setFront(nodeLRU);
}
firstLRU = nodeLRU;
//说明为链表头
nodeLRU.setFront(null);
if (lastLRU == null) {
lastLRU = firstLRU;
nodeLRU.setNext(null);
}
}
/**
* 从FIFO链表删除某一个node
*
* @param nodeFIFO 在FIFO中的node
*/
private void removeFIFO(CacheNode nodeFIFO) {
//从fifo容器中删除
nodesFIFO.remove(nodeFIFO.getKey());
if (lastFIFO == nodeFIFO) {
if (lastFIFO.getFront() != null) {
lastFIFO.getFront().setNext(null);
lastFIFO = lastFIFO.getFront();
} else {
firstFIFO = null;
}
lastFIFO = nodeFIFO.getFront();
} else {
CacheNode index = lastFIFO.getFront();
while (index != null) {
if (index == nodeFIFO) {
if (index.getFront() != null) {
index.getFront().setNext(index.getNext());
} else {
firstFIFO = index.getNext();
}
index.getNext().setFront(index.getFront());
break;
}
//改变下标
index = index.getFront();
}
}
}
/**
* 第二次使用存入lru容器
*
* @param newNode 新的缓存node 从FIFO队列里拿出的node
*/
private void putNodesLRU(CacheNode newNode) {
newNode.setFront(null);
newNode.setNext(null);
if (firstLRU != null) {
firstLRU.setFront(newNode);
newNode.setNext(firstLRU);
}
firstLRU = newNode;
//判断当前内存是否超过
if (getCurrentSize() - initCacheSize > cacheSize) {
if (lastLRU != null) {
nodesLRU.remove(lastLRU.getKey());
if (lastLRU.getFront() != null) {
lastLRU.getFront().setNext(null);
} else {
//第一个也是最后一个
firstLRU = null;
}
lastLRU = lastLRU.getFront();
}
}
nodesLRU.put(newNode.getKey(), newNode);
}
/**
* 获得当前可用内存
*
* @return 可用内存
*/
private Long getCurrentSize() {
return Runtime.getRuntime().freeMemory();
}
private void setCacheSize() {
//从配置文件中获取
String cacheUnit = ConfigurationUtils.getString(GlobalConstants.MEMORY_CACHE_UNIT);
Long cacheSize = Long.valueOf(ConfigurationUtils.getInteger(GlobalConstants.MEMORY_CACHE_SIZE));
switch (cacheUnit) {
case "gb":
this.cacheSize = cacheSize * 1024 * 1024 * 1024;
break;
case "mb":
this.cacheSize = cacheSize * 1024 * 1024;
break;
case "kb":
this.cacheSize = cacheSize * 1024;
break;
case "GB":
this.cacheSize = cacheSize * 1024 * 1024 * 1024;
break;
case "MB":
this.cacheSize = cacheSize * 1024 * 1024;
break;
case "KB":
this.cacheSize = cacheSize * 1024;
break;
default:
log.error("缓存大小单位 " + cacheUnit + " 未识别,请使用kb mb gb");
break;
}
}
public void printLRUSortASC() {
int count = 0;
CacheNode next = firstLRU;
while (next != null) {
count++;
System.out.println("LRU 第 " + count + " 个 key:" + next.getKey() + " value:" + next.getValue());
next = next.getNext();
}
}
public void printFIFOSortASC() {
int count = 0;
CacheNode next = firstFIFO;
while (next != null) {
count++;
System.out.println("FIFO 第 " + count + " 个 key:" + next.getKey() + " value:" + next.getValue());
next = next.getNext();
}
}
}
测试代码:
package dpnice.cache;
import org.junit.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static org.junit.Assert.*;
/**
* @author DPn!ce date 2018 07 13 下午 2:25
*/
public class FastCacheTest {
private FastCache<String, String> fastCache = new FastCache<>();
@Test
public void put() throws Exception {
fastCache.put("id1", "message:缓存的消息1");
fastCache.put("id1", "message:缓存的消息1");
fastCache.put("id2", "message:缓存的消息2");
fastCache.put("id3", "message:缓存的消息3");
fastCache.put("id4", "message:缓存的消息4");
fastCache.put("id1", "message:缓存的消息1");
fastCache.put("id2", "message:缓存的消息2");
fastCache.put("id3", "message:缓存的消息3");
fastCache.put("id4", "message:缓存的消息4");
fastCache.put("id2", "message:缓存的消息2");
fastCache.put("id2", "message:缓存的消息2");
// Thread.sleep(2000L);
System.out.println("id2 ->" + fastCache.get("id2"));
System.out.println("id4 ->" + fastCache.get("id4"));
System.out.println("id1 ->" + fastCache.get("id1"));
System.out.println("id3 ->" + fastCache.get("id3"));
fastCache.printLRUSortASC();
}
@Test
public void get() throws Exception {
System.out.println(fastCache.get("id2"));
System.out.println(fastCache.get("id4"));
System.out.println(fastCache.get("id1"));
System.out.println(fastCache.get("id3"));
}
@Test
public void put2() throws Exception {
for (int i = 1; i < 5; i++) {
fastCache.put("id" + i, "message:缓存的消息" + i);
}
//初始顺序: 4-3-2-1
fastCache.printFIFOSortASC();
//最近访问的在最顶部
System.out.println(fastCache.get("id1"));
System.out.println(fastCache.get("id3"));
//预期顺序: LRU:3-1 FIFO:2-4
fastCache.printLRUSortASC();
fastCache.printFIFOSortASC();
}
@Test
public void multithreadingTest() throws Exception {
for (int i = 0; i < 10000; i++) {
fastCache.put("id" + i, "message:缓存的消息" + i);
}
ExecutorService executor = Executors.newFixedThreadPool(10);
// fastCache.printFIFOSortASC();
//最近访问的在最顶部
for (int i = 0; i < 1000000; i++) {
executor.submit(() -> {
Double v = Math.random() * 10000;
String o = fastCache.get("id" + v.intValue()) + "";
// System.out.println(o);
});
}
// fastCache.printLRUSortASC();
// fastCache.printFIFOSortASC();
}
}
随机获取100W缓存结果: