本博客主要内容为基于ConcurrentHashMap实现一个自定义的缓存工具类。
该缓存工具具有以下功能:
- 支持初始化容量控制
- 支持泛型
- 支持插入具有过期时间的key
- 支持过期key惰性删除
- 支持冷数据淘汰功能
工具类代码如下:
1、缓存工具接口
package com.syx.cache;
import java.util.concurrent.TimeUnit;
public interface Cache<K, V> {
void put(K key, V val, long timeout, TimeUnit timeUnit);
void put(K key, V val);
void clear();
boolean expire(K key);
boolean expireAt(K key, long time, TimeUnit timeUnit);
V get(K key);
int size();
}
2、接口实现工具类
package com.syx.cache;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class CacheUtil<K, V> implements Cache<K, V> {
private static final int DEFAULT_CAPACITY = 50000;
private int capacity;
private static CacheUtil INSTANCE;
private final Object lock = new Object();
private ConcurrentHashMap<K, Value<K, V>> cache;
private CacheUtil(int capacity) {
if (capacity == 0) {
this.capacity = DEFAULT_CAPACITY;
}
this.capacity = capacity;
this.cache = new ConcurrentHashMap<>(this.capacity);
}
@Override
public void put(K key, V val, long timeout, TimeUnit timeUnit) {
eliminate();
Value<K, V> value = new Value<>(key, val, timeout, timeUnit);
cache.put(key, value);
}
@Override
public void put(K key, V val) {
eliminate();
Value<K, V> value = new Value<>(key, val, 0, null);
cache.put(key, value);
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean expire(K key) {
Value<K, V> value = cache.get(key);
if (value == null || value.timeout == 0) {
return false;
}
boolean expire = (System.currentTimeMillis() - value.timestamp) > TimeUnit.MILLISECONDS.convert(value.timeout, value.unit);
if (expire) {
cache.remove(key, value);
}
return expire;
}
@Override
public boolean expireAt(K key, long timeOffset, TimeUnit timeUnit) {
Value<K, V> value = cache.get(key);
if (value == null || value.timeout == 0) {
return false;
}
return TimeUnit.MILLISECONDS.convert(timeOffset, timeUnit) > TimeUnit.MILLISECONDS.convert(value.timeout, value.unit);
}
@Override
public V get(K key) {
if (expire(key)) {
return null;
}
Value<K, V> val = cache.get(key);
if (val == null) return null;
val.count.incrementAndGet();
return val.val;
}
@Override
public int size() {
return cache.size();
}
private void eliminate() {
if (cache.size() >= capacity) {
synchronized (lock) {
Collection<Value<K, V>> values = cache.values();
long expire = values.stream().filter(Value::expire).count();
if ((expire > capacity / 2)) {
cache = new ConcurrentHashMap<>(capacity);
values.stream().filter(e -> !e.expire()).forEach(data -> {
cache.put(data.key, data);
});
} else {
cache = new ConcurrentHashMap<>(capacity);
cache.values().stream()
.filter(e -> !e.expire())
.sorted((o1, o2) -> o2.count.get() - o1.count.get())
.limit(size() / 5).forEach(data -> {
cache.put(data.key, data);
});
}
}
}
}
public List<Map<K, Integer>> top(int top) {
if (top > 100) throw new IllegalArgumentException("top must no more than 100");
return cache.values().stream().sorted((o1, o2) -> o2.count.get() - o1.count.get()).limit(top).map(e -> {
HashMap<K, Integer> map = new HashMap<>();
map.put(e.key, e.count.get());
return map;
}).collect(Collectors.toList());
}
public int getCapacity() {
return capacity;
}
public static CacheUtil getINSTANCE(int capacity) {
if (INSTANCE == null) {
synchronized (CacheUtil.class) {
if (INSTANCE == null) {
INSTANCE = new CacheUtil(capacity);
}
}
}
return INSTANCE;
}
private static class Value<K, V> {
private K key;
private V val;
private long timestamp;
private long timeout;
private TimeUnit unit;
private AtomicInteger count;
public Value(K key, V val, long timeout, TimeUnit unit) {
this.key = key;
this.val = val;
this.timestamp = System.currentTimeMillis();
this.timeout = timeout;
this.unit = unit;
this.count = new AtomicInteger();
}
public boolean expire() {
return (System.currentTimeMillis() - timestamp) > TimeUnit.MILLISECONDS.convert(timeout, unit);
}
public K getKey() {
return key;
}
public V getVal() {
return val;
}
public long getTimestamp() {
return timestamp;
}
public long getTimeout() {
return timeout;
}
public TimeUnit getUnit() {
return unit;
}
public AtomicInteger getCount() {
return count;
}
@Override
public String toString() {
return "Value{" +
"key=" + key +
", val=" + val +
", timestamp=" + timestamp +
", timeout=" + timeout +
", unit=" + unit +
", count=" + count +
'}';
}
}
}
3、测试
import com.syx.cache.CacheUtil;
import java.util.concurrent.TimeUnit;
public class CacheTest {
public static void main(String[] args) throws InterruptedException {
fn1();
}
public static void fn1() throws InterruptedException {
CacheUtil<String,String> cache = CacheUtil.getINSTANCE(10);
cache.put("key","whh",10,TimeUnit.MILLISECONDS);
cache.put("ls","ls");
cache.put("wz","wz");
cache.put("ww","ww");
System.out.println(cache.expire("key"));
Thread.sleep(12);
System.out.println(cache.expire("key"));
System.out.println(cache.size());
cache.get("wz");
cache.get("wz");
cache.get("ls");
System.out.println(cache.top(10));
}
}
4、测试结果

目前该工具还需进一步压测,以测试其性能,如有问题请大家指正。