一、前言
关于Hutool定义cn.hutool.cache.Cache缓存接口标准,并定义常用cn.hutool.cache.CacheUtil缓存工具类,实现FIFO(first in first out) 先进先出缓存、LFU(least frequently used) 最少使用率缓存、LRU (least recently used)最近最久未使用缓存、TimedCache定时缓存、WeakCache弱引用缓存及NoCache无缓存各个不同场景缓存需求实现。
--FIFO(first in first out) 先进先出缓存@b@cn.hutool.cache.impl.FIFOCache;@b@@b@--LFU(least frequently used) 最少使用率缓存.@b@cn.hutool.cache.impl.LFUCache;@b@@b@--LRU (least recently used)最近最久未使用缓存.@b@cn.hutool.cache.impl.LRUCache;@b@@b@--无缓存实现.@b@cn.hutool.cache.impl.NoCache;@b@@b@--定时缓存.@b@cn.hutool.cache.impl.TimedCache;@b@@b@--弱引用缓存.@b@cn.hutool.cache.impl.WeakCache;
二、源码示例
1、定义缓存Cache接口
package cn.hutool.cache;@b@@b@import cn.hutool.cache.impl.CacheObj;@b@import cn.hutool.core.lang.func.Func0;@b@@b@import java.io.Serializable;@b@import java.util.Iterator;@b@@b@/**@b@ * 缓存接口@b@ *@b@ * @param <K> 键类型@b@ * @param <V> 值类型@b@ * @author Looly, jodd@b@ */@b@public interface Cache<K, V> extends Iterable<V>, Serializable {@b@@b@ /**@b@ * 返回缓存容量,{@code 0}表示无大小限制@b@ *@b@ * @return 返回缓存容量,{@code 0}表示无大小限制@b@ */@b@ int capacity();@b@@b@ /**@b@ * 缓存失效时长, {@code 0} 表示没有设置,单位毫秒@b@ *@b@ * @return 缓存失效时长, {@code 0} 表示没有设置,单位毫秒@b@ */@b@ long timeout();@b@@b@ /**@b@ * 将对象加入到缓存,使用默认失效时长@b@ *@b@ * @param key 键@b@ * @param object 缓存的对象@b@ * @see Cache#put(Object, Object, long)@b@ */@b@ void put(K key, V object);@b@@b@ /**@b@ * 将对象加入到缓存,使用指定失效时长<br>@b@ * 如果缓存空间满了,{@link #prune()} 将被调用以获得空间来存放新对象@b@ *@b@ * @param key 键@b@ * @param object 缓存的对象@b@ * @param timeout 失效时长,单位毫秒@b@ */@b@ void put(K key, V object, long timeout);@b@@b@ /**@b@ * 从缓存中获得对象,当对象不在缓存中或已经过期返回{@code null}@b@ * <p>@b@ * 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回{@code null},否则返回值。@b@ * <p>@b@ * 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。@b@ *@b@ * @param key 键@b@ * @return 键对应的对象@b@ * @see #get(Object, boolean)@b@ */@b@ default V get(K key) {@b@ return get(key, true);@b@ }@b@@b@ /**@b@ * 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象@b@ * <p>@b@ * 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回{@code null},否则返回值。@b@ * <p>@b@ * 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。@b@ *@b@ * @param key 键@b@ * @param supplier 如果不存在回调方法,用于生产值对象@b@ * @return 值对象@b@ */@b@ default V get(K key, Func0<V> supplier) {@b@ return get(key, true, supplier);@b@ }@b@@b@ /**@b@ * 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象@b@ * <p>@b@ * 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回{@code null},否则返回值。@b@ * <p>@b@ * 每次调用此方法会可选是否刷新最后访问时间,{@code true}表示会重新计算超时时间。@b@ *@b@ * @param key 键@b@ * @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。@b@ * @param supplier 如果不存在回调方法,用于生产值对象@b@ * @return 值对象@b@ */@b@ V get(K key, boolean isUpdateLastAccess, Func0<V> supplier);@b@@b@ /**@b@ * 从缓存中获得对象,当对象不在缓存中或已经过期返回{@code null}@b@ * <p>@b@ * 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回{@code null},否则返回值。@b@ * <p>@b@ * 每次调用此方法会可选是否刷新最后访问时间,{@code true}表示会重新计算超时时间。@b@ *@b@ * @param key 键@b@ * @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。@b@ * @return 键对应的对象@b@ */@b@ V get(K key, boolean isUpdateLastAccess);@b@@b@ /**@b@ * 返回包含键和值得迭代器@b@ *@b@ * @return 缓存对象迭代器@b@ * @since 4.0.10@b@ */@b@ Iterator<CacheObj<K, V>> cacheObjIterator();@b@@b@ /**@b@ * 从缓存中清理过期对象,清理策略取决于具体实现@b@ *@b@ * @return 清理的缓存对象个数@b@ */@b@ int prune();@b@@b@ /**@b@ * 缓存是否已满,仅用于有空间限制的缓存对象@b@ *@b@ * @return 缓存是否已满,仅用于有空间限制的缓存对象@b@ */@b@ boolean isFull();@b@@b@ /**@b@ * 从缓存中移除对象@b@ *@b@ * @param key 键@b@ */@b@ void remove(K key);@b@@b@ /**@b@ * 清空缓存@b@ */@b@ void clear();@b@@b@ /**@b@ * 缓存的对象数量@b@ *@b@ * @return 缓存的对象数量@b@ */@b@ int size();@b@@b@ /**@b@ * 缓存是否为空@b@ *@b@ * @return 缓存是否为空@b@ */@b@ boolean isEmpty();@b@@b@ /**@b@ * 是否包含key@b@ *@b@ * @param key KEY@b@ * @return 是否包含key@b@ */@b@ boolean containsKey(K key);@b@@b@ /**@b@ * 设置监听@b@ *@b@ * @param listener 监听@b@ * @return this@b@ * @since 5.5.2@b@ */@b@ default Cache<K, V> setListener(CacheListener<K, V> listener){@b@ return this;@b@ }@b@}
2、定义CacheUtil实现不同缓存代码
package cn.hutool.cache;@b@@b@import cn.hutool.cache.impl.FIFOCache;@b@import cn.hutool.cache.impl.LFUCache;@b@import cn.hutool.cache.impl.LRUCache;@b@import cn.hutool.cache.impl.NoCache;@b@import cn.hutool.cache.impl.TimedCache;@b@import cn.hutool.cache.impl.WeakCache;@b@@b@/**@b@ * 缓存工具类@b@ * @author Looly@b@ *@since 3.0.1@b@ */@b@public class CacheUtil {@b@@b@ /**@b@ * 创建FIFO(first in first out) 先进先出缓存.@b@ *@b@ * @param <K> Key类型@b@ * @param <V> Value类型@b@ * @param capacity 容量@b@ * @param timeout 过期时长,单位:毫秒@b@ * @return {@link FIFOCache}@b@ */@b@ public static <K, V> FIFOCache<K, V> newFIFOCache(int capacity, long timeout){@b@ return new FIFOCache<>(capacity, timeout);@b@ }@b@@b@ /**@b@ * 创建FIFO(first in first out) 先进先出缓存.@b@ *@b@ * @param <K> Key类型@b@ * @param <V> Value类型@b@ * @param capacity 容量@b@ * @return {@link FIFOCache}@b@ */@b@ public static <K, V> FIFOCache<K, V> newFIFOCache(int capacity){@b@ return new FIFOCache<>(capacity);@b@ }@b@@b@ /**@b@ * 创建LFU(least frequently used) 最少使用率缓存.@b@ *@b@ * @param <K> Key类型@b@ * @param <V> Value类型@b@ * @param capacity 容量@b@ * @param timeout 过期时长,单位:毫秒@b@ * @return {@link LFUCache}@b@ */@b@ public static <K, V> LFUCache<K, V> newLFUCache(int capacity, long timeout){@b@ return new LFUCache<>(capacity, timeout);@b@ }@b@@b@ /**@b@ * 创建LFU(least frequently used) 最少使用率缓存.@b@ *@b@ * @param <K> Key类型@b@ * @param <V> Value类型@b@ * @param capacity 容量@b@ * @return {@link LFUCache}@b@ */@b@ public static <K, V> LFUCache<K, V> newLFUCache(int capacity){@b@ return new LFUCache<>(capacity);@b@ }@b@@b@@b@ /**@b@ * 创建LRU (least recently used)最近最久未使用缓存.@b@ *@b@ * @param <K> Key类型@b@ * @param <V> Value类型@b@ * @param capacity 容量@b@ * @param timeout 过期时长,单位:毫秒@b@ * @return {@link LRUCache}@b@ */@b@ public static <K, V> LRUCache<K, V> newLRUCache(int capacity, long timeout){@b@ return new LRUCache<>(capacity, timeout);@b@ }@b@@b@ /**@b@ * 创建LRU (least recently used)最近最久未使用缓存.@b@ *@b@ * @param <K> Key类型@b@ * @param <V> Value类型@b@ * @param capacity 容量@b@ * @return {@link LRUCache}@b@ */@b@ public static <K, V> LRUCache<K, V> newLRUCache(int capacity){@b@ return new LRUCache<>(capacity);@b@ }@b@@b@ /**@b@ * 创建定时缓存.@b@ *@b@ * @param <K> Key类型@b@ * @param <V> Value类型@b@ * @param timeout 过期时长,单位:毫秒@b@ * @return {@link TimedCache}@b@ */@b@ public static <K, V> TimedCache<K, V> newTimedCache(long timeout){@b@ return new TimedCache<>(timeout);@b@ }@b@@b@ /**@b@ * 创建弱引用缓存.@b@ *@b@ * @param <K> Key类型@b@ * @param <V> Value类型@b@ * @param timeout 过期时长,单位:毫秒@b@ * @return {@link WeakCache}@b@ * @since 3.0.7@b@ */@b@ public static <K, V> WeakCache<K, V> newWeakCache(long timeout){@b@ return new WeakCache<>(timeout);@b@ }@b@@b@ /**@b@ * 创建无缓存实现.@b@ *@b@ * @param <K> Key类型@b@ * @param <V> Value类型@b@ * @return {@link NoCache}@b@ */@b@ public static <K, V> NoCache<K, V> newNoCache(){@b@ return new NoCache<>();@b@ }@b@}
3、公共抽象缓存实现类AbstractCache
package cn.hutool.cache.impl;@b@@b@import cn.hutool.cache.Cache;@b@import cn.hutool.cache.CacheListener;@b@import cn.hutool.core.collection.CopiedIter;@b@import cn.hutool.core.lang.func.Func0;@b@@b@import java.util.Iterator;@b@import java.util.Map;@b@import java.util.Set;@b@import java.util.concurrent.ConcurrentHashMap;@b@import java.util.concurrent.atomic.LongAdder;@b@import java.util.concurrent.locks.Lock;@b@import java.util.concurrent.locks.ReentrantLock;@b@import java.util.concurrent.locks.StampedLock;@b@@b@/**@b@ * 超时和限制大小的缓存的默认实现<br>@b@ * 继承此抽象缓存需要:<br>@b@ * <ul>@b@ * <li>创建一个新的Map</li>@b@ * <li>实现 {@code prune} 策略</li>@b@ * </ul>@b@ *@b@ * @param <K> 键类型@b@ * @param <V> 值类型@b@ * @author Looly, jodd@b@ */@b@public abstract class AbstractCache<K, V> implements Cache<K, V> {@b@ private static final long serialVersionUID = 1L;@b@@b@ protected Map<K, CacheObj<K, V>> cacheMap;@b@@b@ // 乐观锁,此处使用乐观锁解决读多写少的场景@b@ // get时乐观读,再检查是否修改,修改则转入悲观读重新读一遍,可以有效解决在写时阻塞大量读操作的情况。@b@ // see: https://www.cnblogs.com/jiagoushijuzi/p/13721319.html@b@ private final StampedLock lock = new StampedLock();@b@@b@ /**@b@ * 写的时候每个key一把锁,降低锁的粒度@b@ */@b@ protected final Map<K, Lock> keyLockMap = new ConcurrentHashMap<>();@b@@b@ /**@b@ * 返回缓存容量,{@code 0}表示无大小限制@b@ */@b@ protected int capacity;@b@ /**@b@ * 缓存失效时长, {@code 0} 表示无限制,单位毫秒@b@ */@b@ protected long timeout;@b@@b@ /**@b@ * 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。@b@ */@b@ protected boolean existCustomTimeout;@b@@b@ /**@b@ * 命中数,即命中缓存计数@b@ */@b@ protected LongAdder hitCount = new LongAdder();@b@ /**@b@ * 丢失数,即未命中缓存计数@b@ */@b@ protected LongAdder missCount = new LongAdder();@b@@b@ /**@b@ * 缓存监听@b@ */@b@ protected CacheListener<K, V> listener;@b@@b@ // ---------------------------------------------------------------- put start@b@ @Override@b@ public void put(K key, V object) {@b@ put(key, object, timeout);@b@ }@b@@b@ @Override@b@ public void put(K key, V object, long timeout) {@b@ final long stamp = lock.writeLock();@b@ try {@b@ putWithoutLock(key, object, timeout);@b@ } finally {@b@ lock.unlockWrite(stamp);@b@ }@b@ }@b@@b@ /**@b@ * 加入元素,无锁@b@ *@b@ * @param key 键@b@ * @param object 值@b@ * @param timeout 超时时长@b@ * @since 4.5.16@b@ */@b@ private void putWithoutLock(K key, V object, long timeout) {@b@ CacheObj<K, V> co = new CacheObj<>(key, object, timeout);@b@ if (timeout != 0) {@b@ existCustomTimeout = true;@b@ }@b@ if (isFull()) {@b@ pruneCache();@b@ }@b@ cacheMap.put(key, co);@b@ }@b@ // ---------------------------------------------------------------- put end@b@@b@ // ---------------------------------------------------------------- get start@b@ @Override@b@ public boolean containsKey(K key) {@b@ final long stamp = lock.readLock();@b@ try {@b@ // 不存在或已移除@b@ final CacheObj<K, V> co = cacheMap.get(key);@b@ if (co == null) {@b@ return false;@b@ }@b@@b@ if (false == co.isExpired()) {@b@ // 命中@b@ return true;@b@ }@b@ } finally {@b@ lock.unlockRead(stamp);@b@ }@b@@b@ // 过期@b@ remove(key, true);@b@ return false;@b@ }@b@@b@ /**@b@ * @return 命中数@b@ */@b@ public long getHitCount() {@b@ return hitCount.sum();@b@ }@b@@b@ /**@b@ * @return 丢失数@b@ */@b@ public long getMissCount() {@b@ return missCount.sum();@b@ }@b@@b@ @Override@b@ public V get(K key, boolean isUpdateLastAccess, Func0<V> supplier) {@b@ V v = get(key, isUpdateLastAccess);@b@ if (null == v && null != supplier) {@b@ //每个key单独获取一把锁,降低锁的粒度提高并发能力,see pr#1385@Github@b@ final Lock keyLock = keyLockMap.computeIfAbsent(key, k -> new ReentrantLock());@b@ keyLock.lock();@b@ try {@b@ // 双重检查锁,防止在竞争锁的过程中已经有其它线程写入@b@ final CacheObj<K, V> co = cacheMap.get(key);@b@ if (null == co || co.isExpired()) {@b@ try {@b@ v = supplier.call();@b@ } catch (Exception e) {@b@ throw new RuntimeException(e);@b@ }@b@ put(key, v, this.timeout);@b@ } else {@b@ v = co.get(isUpdateLastAccess);@b@ }@b@ } finally {@b@ keyLock.unlock();@b@ keyLockMap.remove(key);@b@ }@b@ }@b@ return v;@b@ }@b@@b@ @Override@b@ public V get(K key, boolean isUpdateLastAccess) {@b@ // 尝试读取缓存,使用乐观读锁@b@ long stamp = lock.tryOptimisticRead();@b@ CacheObj<K, V> co = cacheMap.get(key);@b@ if(false == lock.validate(stamp)){@b@ // 有写线程修改了此对象,悲观读@b@ stamp = lock.readLock();@b@ try {@b@ co = cacheMap.get(key);@b@ } finally {@b@ lock.unlockRead(stamp);@b@ }@b@ }@b@@b@ // 未命中@b@ if (null == co) {@b@ missCount.increment();@b@ return null;@b@ } else if (false == co.isExpired()) {@b@ hitCount.increment();@b@ return co.get(isUpdateLastAccess);@b@ }@b@@b@ // 过期,既不算命中也不算非命中@b@ remove(key, true);@b@ return null;@b@ }@b@@b@ // ---------------------------------------------------------------- get end@b@@b@ @Override@b@ public Iterator<V> iterator() {@b@ CacheObjIterator<K, V> copiedIterator = (CacheObjIterator<K, V>) this.cacheObjIterator();@b@ return new CacheValuesIterator<>(copiedIterator);@b@ }@b@@b@ @Override@b@ public Iterator<CacheObj<K, V>> cacheObjIterator() {@b@ CopiedIter<CacheObj<K, V>> copiedIterator;@b@ final long stamp = lock.readLock();@b@ try {@b@ copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());@b@ } finally {@b@ lock.unlockRead(stamp);@b@ }@b@ return new CacheObjIterator<>(copiedIterator);@b@ }@b@@b@ // ---------------------------------------------------------------- prune start@b@@b@ /**@b@ * 清理实现<br>@b@ * 子类实现此方法时无需加锁@b@ *@b@ * @return 清理数@b@ */@b@ protected abstract int pruneCache();@b@@b@ @Override@b@ public final int prune() {@b@ final long stamp = lock.writeLock();@b@ try {@b@ return pruneCache();@b@ } finally {@b@ lock.unlockWrite(stamp);@b@ }@b@ }@b@ // ---------------------------------------------------------------- prune end@b@@b@ // ---------------------------------------------------------------- common start@b@ @Override@b@ public int capacity() {@b@ return capacity;@b@ }@b@@b@ /**@b@ * @return 默认缓存失效时长。<br>@b@ * 每个对象可以单独设置失效时长@b@ */@b@ @Override@b@ public long timeout() {@b@ return timeout;@b@ }@b@@b@ /**@b@ * 只有设置公共缓存失效时长或每个对象单独的失效时长时清理可用@b@ *@b@ * @return 过期对象清理是否可用,内部使用@b@ */@b@ protected boolean isPruneExpiredActive() {@b@ return (timeout != 0) || existCustomTimeout;@b@ }@b@@b@ @Override@b@ public boolean isFull() {@b@ return (capacity > 0) && (cacheMap.size() >= capacity);@b@ }@b@@b@ @Override@b@ public void remove(K key) {@b@ remove(key, false);@b@ }@b@@b@ @Override@b@ public void clear() {@b@ final long stamp = lock.writeLock();@b@ try {@b@ cacheMap.clear();@b@ } finally {@b@ lock.unlockWrite(stamp);@b@ }@b@ }@b@@b@ @Override@b@ public int size() {@b@ return cacheMap.size();@b@ }@b@@b@ @Override@b@ public boolean isEmpty() {@b@ return cacheMap.isEmpty();@b@ }@b@@b@ @Override@b@ public String toString() {@b@ return this.cacheMap.toString();@b@ }@b@ // ---------------------------------------------------------------- common end@b@@b@ /**@b@ * 设置监听@b@ *@b@ * @param listener 监听@b@ * @return this@b@ * @since 5.5.2@b@ */@b@ @Override@b@ public AbstractCache<K, V> setListener(CacheListener<K, V> listener) {@b@ this.listener = listener;@b@ return this;@b@ }@b@@b@ /**@b@ * 返回所有键@b@ *@b@ * @return 所有键@b@ * @since 5.5.9@b@ */@b@ public Set<K> keySet(){@b@ return this.cacheMap.keySet();@b@ }@b@@b@ /**@b@ * 对象移除回调。默认无动作<br>@b@ * 子类可重写此方法用于监听移除事件,如果重写,listener将无效@b@ *@b@ * @param key 键@b@ * @param cachedObject 被缓存的对象@b@ */@b@ protected void onRemove(K key, V cachedObject) {@b@ final CacheListener<K, V> listener = this.listener;@b@ if (null != listener) {@b@ listener.onRemove(key, cachedObject);@b@ }@b@ }@b@@b@ /**@b@ * 移除key对应的对象@b@ *@b@ * @param key 键@b@ * @param withMissCount 是否计数丢失数@b@ */@b@ private void remove(K key, boolean withMissCount) {@b@ final long stamp = lock.writeLock();@b@ CacheObj<K, V> co;@b@ try {@b@ co = removeWithoutLock(key, withMissCount);@b@ } finally {@b@ lock.unlockWrite(stamp);@b@ }@b@ if (null != co) {@b@ onRemove(co.key, co.obj);@b@ }@b@ }@b@@b@ /**@b@ * 移除key对应的对象,不加锁@b@ *@b@ * @param key 键@b@ * @param withMissCount 是否计数丢失数@b@ * @return 移除的对象,无返回null@b@ */@b@ private CacheObj<K, V> removeWithoutLock(K key, boolean withMissCount) {@b@ final CacheObj<K, V> co = cacheMap.remove(key);@b@ if (withMissCount) {@b@ // 在丢失计数有效的情况下,移除一般为get时的超时操作,此处应该丢失数+1@b@ this.missCount.increment();@b@ }@b@ return co;@b@ }@b@}