功能目标

     实现一个全局范围的LocalCache,各个业务点使用自己的Namespace对LocalCache进行逻辑分区。所以在LocalCache中进行读写採用的key为(namespace+(分隔符)+数据key)。如存在下面的一对keyValue :  NameToAge,Troy -> 23 。要求LocalCache线程安全,且LocalCache中总keyValue数量可控,提供清空,调整大小,dump到本地文件等一系列操作。


用LinkedHashMap实现LRU Map

     LinkedHashMap提供了键值对的储存功能,且可依据其支持訪问排序的特性来模拟LRU算法。简单来说,LinkedHashMap在訪问已存在元素或插入新元素时,会将该元素放置在链表的尾部,所以在链表头部的元素是近期最少未使用的元素,而这正是LRU算法的描写叙述。因为其底层基于链表实现,所以对于元素的移动和插入操作性能表现优异。我们将利用一个LinkedHashMap实现一个线程安全的LRU
Map。

LRU Map的实现

public class LRUMap<T> extends LinkedHashMap<String, SoftReference<T>> implements Externalizable {

    private static final long serialVersionUID = -7076355612133906912L;

    /** The maximum size of the cache. */
private int maxCacheSize; /* lock for map */
private final Lock lock = new ReentrantLock(); /**
* 默认构造函数,LRUMap的大小为Integer.MAX_VALUE
*/
public LRUMap() {
super();
maxCacheSize = Integer.MAX_VALUE;
} /**
* Constructs a new, empty cache with the specified maximum size.
*/
public LRUMap(int size) {
super(size + 1, 1f, true);
maxCacheSize = size;
} /**
* 让LinkHashMap支持LRU。假设Map的大小超过了预定值,则返回true,LinkedHashMap自身实现返回
* fasle。即永远不删除元素
*/
@Override
protected boolean removeEldestEntry(Map.Entry<String, SoftReference<T>> eldest) {
boolean tmp = (size() > maxCacheSize);
return tmp;
} public T addEntry(String key, T entry) {
try {
SoftReference<T> sr_entry = new SoftReference<T>(entry);
// add entry to hashmap
lock.lock();
put(key, sr_entry);
}
finally {
lock.unlock();
}
return entry;
} public T getEntry(String key) {
SoftReference<T> sr_entry;
try {
lock.lock();
if ((sr_entry = get(key)) == null)
return null;
// if soft reference is null then the entry has been
// garbage collected and so the key should be removed also.
if (sr_entry.get() == null) {
remove(key);
return null;
}
}
finally {
lock.unlock();
}
return sr_entry.get();
} @Override
public SoftReference<T> remove(Object key) {
try {
lock.lock();
return super.remove(key);
}
finally {
lock.unlock();
}
} @Override
public synchronized void clear() {
super.clear();
} public void writeExternal(ObjectOutput out) throws IOException {
Iterator<Map.Entry<String, SoftReference<T>>> i = (size() > 0) ? entrySet().iterator() : null;
// Write out size
out.writeInt(size());
// Write out keys and values
if (i != null) {
while (i.hasNext()) {
Map.Entry<String, SoftReference<T>> e = i.next();
if (e != null && e.getValue() != null && e.getValue().get() != null) {
out.writeObject(e.getKey());
out.writeObject(e.getValue().get());
}
}
}
} public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// Read in size
int size = in.readInt();
// Read the keys and values, and put the mappings in the Map
for (int i = 0; i < size; i++) {
String key = (String) in.readObject();
@SuppressWarnings("unchecked")
T value = (T) in.readObject();
addEntry(key, value);
}
} }

LocalCache设计

     假设在LocalCache中仅仅使用一个LRU Map。将产生性能问题:1. 单个LinkedHashMap中元素数量太多 2. 高并发下读写锁限制。

     所以能够在LocalCache中使用多个LRU Map,并使用key 来 hash到某个LRU Map上,以此来提高在单个LinkedHashMap中检索的速度以及提高总体并发度。

LocalCache实现

     这里hash选用了Wang/Jenkins hash算法。实现Hash的方式參考了ConcurrentHashMap的实现。
public class LocalCache{

     private final int size;
/**
* 本地缓存最大容量
*/
static final int MAXIMUM_CAPACITY = 1 << 30; /**
* 本地缓存支持最大的分区数
*/
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative /**
* 本地缓存存储的LRUMap数组
*/
LRUMap<CacheObject>[] segments; /**
* Mask value for indexing into segments. The upper bits of a key's hash
* code are used to choose the segment.
*/
int segmentMask; /**
* Shift value for indexing within segments.
*/
int segmentShift; /**
*
* 计数器重置阀值
*/
private static final int MAX_LOOKUP = 100000000; /**
* 用于重置计数器的锁。防止多次重置计数器
*/
private final Lock lock = new ReentrantLock(); /**
* Number of requests made to lookup a cache entry.
*/
private AtomicLong lookup = new AtomicLong(0); /**
* Number of successful requests for cache entries.
*/
private AtomicLong found = new AtomicLong(0); public LocalCacheServiceImpl(int size) {
this.size = size;
} public CacheObject get(String key) {
if (StringUtils.isBlank(key)) {
return null;
}
// 添加计数器
lookup.incrementAndGet(); // 假设必要重置计数器
if (lookup.get() > MAX_LOOKUP) {
if (lock.tryLock()) {
try {
lookup.set(0);
found.set(0);
}
finally {
lock.unlock();
}
}
} int hash = hash(key.hashCode());
CacheObject ret = segmentFor(hash).getEntry(key);
if (ret != null)
found.incrementAndGet();
return ret;
} public void remove(String key) {
if (StringUtils.isBlank(key)) {
return;
}
int hash = hash(key.hashCode());
segmentFor(hash).remove(key);
return;
} public void put(String key, CacheObject val) {
if (StringUtils.isBlank(key) || val == null) {
return;
}
int hash = hash(key.hashCode());
segmentFor(hash).addEntry(key, val);
return;
} public synchronized void clearCache() {
for (int i = 0; i < segments.length; ++i)
segments[i].clear();
} public synchronized void reload() throws Exception {
clearCache();
init();
} public synchronized void dumpLocalCache() throws Exception {
for (int i = 0; i < segments.length; ++i) {
String tmpDir = System.getProperty("java.io.tmpdir");
String fileName = tmpDir + File.separator + "localCache-dump-file" + i + ".cache";
File file = new File(fileName);
ObjectUtils.objectToFile(segments[i], file);
}
} @SuppressWarnings("unchecked")
public synchronized void restoreLocalCache() throws Exception {
for (int i = 0; i < segments.length; ++i) {
String tmpDir = System.getProperty("java.io.tmpdir");
String fileName = tmpDir + File.separator + "localCache-dump-file" + i + ".cache";
File file = new File(fileName);
LRUMap<CacheObject> lruMap = (LRUMap<CacheObject>) ObjectUtils.fileToObject(file);
if (lruMap != null) {
Set<Entry<String, SoftReference<CacheObject>>> set = lruMap.entrySet();
Iterator<Entry<String, SoftReference<CacheObject>>> it = set.iterator();
while (it.hasNext()) {
Entry<String, SoftReference<CacheObject>> entry = it.next();
if (entry.getValue() != null && entry.getValue().get() != null)
segments[i].addEntry(entry.getKey(), entry.getValue().get());
}
}
}
} /**
* 本地缓存命中次数,在计数器RESET的时刻可能会出现0的命中率
*/
public int getHitRate() {
long query = lookup.get();
return query == 0 ? 0 : (int) ((found.get() * 100) / query);
} /**
* 本地缓存訪问次数。在计数器RESET时可能会出现0的查找次数
*/
public long getCount() {
return lookup.get();
} public int size() {
final LRUMap<CacheObject>[] segments = this.segments;
long sum = 0;
for (int i = 0; i < segments.length; ++i) {
sum += segments[i].size();
}
if (sum > Integer.MAX_VALUE)
return Integer.MAX_VALUE;
else
return (int) sum;
} /**
* Returns the segment that should be used for key with given hash
*
* @param hash
* the hash code for the key
* @return the segment
*/
final LRUMap<CacheObject> segmentFor(int hash) {
return segments[(hash >>> segmentShift) & segmentMask];
} /* ---------------- Small Utilities -------------- */ /**
* Applies a supplemental hash function to a given hashCode, which defends
* against poor quality hash functions. This is critical because
* ConcurrentHashMap uses power-of-two length hash tables, that otherwise
* encounter collisions for hashCodes that do not differ in lower or upper
* bits.
*/
private static int hash(int h) {
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
} @SuppressWarnings("unchecked")
public void init() throws Exception {
int concurrencyLevel = 16;
int capacity = size;
if (capacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
segmentShift = 32 - sshift;
segmentMask = ssize - 1;
this.segments = new LRUMap[ssize];
if (capacity > MAXIMUM_CAPACITY)
capacity = MAXIMUM_CAPACITY;
int c = capacity / ssize;
if (c * ssize < capacity)
++c;
int cap = 1;
while (cap < c)
cap <<= 1;
cap >>= 1;
for (int i = 0; i < this.segments.length; ++i)
this.segments[i] = new LRUMap<CacheObject>(cap);
}
}

手写一个自己的LocalCache - 基于LinkedHashMap实现LRU的更多相关文章

  1. 教你如何使用Java手写一个基于链表的队列

    在上一篇博客[教你如何使用Java手写一个基于数组的队列]中已经介绍了队列,以及Java语言中对队列的实现,对队列不是很了解的可以我上一篇文章.那么,现在就直接进入主题吧. 这篇博客主要讲解的是如何使 ...

  2. 放弃antd table,基于React手写一个虚拟滚动的表格

    缘起 标题有点夸张,并不是完全放弃antd-table,毕竟在react的生态圈里,对国人来说,比较好用的PC端组件库,也就antd了.即便经历了2018年圣诞彩蛋事件,antd的使用者也不仅不减,反 ...

  3. 搞定redis面试--Redis的过期策略?手写一个LRU?

    1 面试题 Redis的过期策略都有哪些?内存淘汰机制都有哪些?手写一下LRU代码实现? 2 考点分析 1)我往redis里写的数据怎么没了? 我们生产环境的redis怎么经常会丢掉一些数据?写进去了 ...

  4. webview的简单介绍和手写一个H5套壳的webview

    1.webview是什么?作用是什么?和浏览器有什么关系? Webview 是一个基于webkit引擎,可以解析DOM 元素,展示html页面的控件,它和浏览器展示页面的原理是相同的,所以可以把它当做 ...

  5. 摊牌了!我要手写一个“Spring Boot”

    目前的话,已经把 Spring MVC 相关常用的注解比如@GetMapping .@PostMapping .@PathVariable 写完了.我也已经将项目开源出来了,地址:https://gi ...

  6. 浅析MyBatis(二):手写一个自己的MyBatis简单框架

    在上一篇文章中,我们由一个快速案例剖析了 MyBatis 的整体架构与整体运行流程,在本篇文章中笔者会根据 MyBatis 的运行流程手写一个自定义 MyBatis 简单框架,在实践中加深对 MyBa ...

  7. 手写一个LRU工具类

    LRU概述 LRU算法,即最近最少使用算法.其使用场景非常广泛,像我们日常用的手机的后台应用展示,软件的复制粘贴板等. 本文将基于算法思想手写一个具有LRU算法功能的Java工具类. 结构设计 在插入 ...

  8. 【redis前传】自己手写一个LRU策略 | redis淘汰策略

    title: 自己手写一个LRU策略 date: 2021-06-18 12:00:30 tags: - [redis] - [lru] categories: - [redis] permalink ...

  9. 『练手』手写一个独立Json算法 JsonHelper

    背景: > 一直使用 Newtonsoft.Json.dll 也算挺稳定的. > 但这个框架也挺闹心的: > 1.影响编译失败:https://www.cnblogs.com/zih ...

随机推荐

  1. date time insert

    DATE=`date '+%m/%d/%Y'`TIME=`date '+%H:%M:%S'` sed -i '1i1***** start*****' test.kshsed -i '2i\ REPO ...

  2. Swift2.0语言教程之Swift2.0语言中的标准函数

    Swift2.0语言教程之Swift2.0语言中的标准函数 Swift2.0中的标准函数 函数除了可以根据参数列表的有无分为无参函数和有参函数,还可以从定义角度分为用户自定义函数和标准函数两种.以上的 ...

  3. 2018 计蒜之道复赛 贝壳找房魔法师顾问(并查集+dfs判环)

    贝壳找房在遥远的传奇境外,找到了一个强大的魔法师顾问.他有 22 串数量相同的法力水晶,每个法力水晶可能有不同的颜色.为了方便起见,可以将每串法力水晶视为一个长度不大于 10^5105,字符集不大于  ...

  4. 【搜索+DP】codevs1066-引水入城

    [题目大意] 一个N行M列的矩形,如上图所示,其中每个格子都代表一座城 市,每座城市都有一个海拔高度.现在要在某些城市建造水利设施.水利设施有两种,分别为蓄水厂和输水站.蓄水厂的功能是利用水泵将湖泊中 ...

  5. 压缩的问题-----WriteUp

    原题:http://ctf5.shiyanbar.com/crypto/winrar/ 526172211A0700CF907300000D0000000000000056947424965E 006 ...

  6. 【洛谷】4310: 绝世好题【二进制DP】

    P4310 绝世好题 题目描述 给定一个长度为n的数列ai,求ai的子序列bi的最长长度,满足bi&bi-1!=0(2<=i<=len). 输入输出格式 输入格式: 输入文件共2行 ...

  7. bzoj 3545/3551: [ONTAK2010]Peaks -- 主席树,最小生成树,倍增

    3545: [ONTAK2010]Peaks Time Limit: 10 Sec  Memory Limit: 128 MB Description 在Bytemountains有N座山峰,每座山峰 ...

  8. Codeforces Round #295 (Div. 2)B - Two Buttons BFS

    B. Two Buttons time limit per test 2 seconds memory limit per test 256 megabytes input standard inpu ...

  9. Install WordPress Plugins without FTP Access

    WordPress will only prompt you for your FTP connection information while trying to install plugins o ...

  10. oracle sql语句怎么查询所有存储过程中是否包含某个注释?

    select text from all_source where type='PROCEDDURE' and name='过程名'and instr(text,'注释内容')>0