使用WeakHashMap改进:
WeakHashMap类在java.util包内,它实现了Map接口,是HashMap的一种实现(但不是它的子类),它使用弱引用作为内部数据的存储方案。WeakHashMap是弱引用的一种典型应用,它可以作为简单的缓存表解决方案。
// @since 1.2 JDK1.2出来的 public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> { // 内部自己new了一个ReferenceQueue private final ReferenceQueue<Object> queue = new ReferenceQueue<>(); ... // 它使用若引用的核心主要是Entry这个内类 // 继承自WeakReference,这样一来,整个Entry就是一个WeakReferenc private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> { Entry(Object key, V value,ReferenceQueue<Object> queue,int hash, Entry<K,V> next) { super(key, queue); // 调用WeakReference的构造函数 this.value = value; this.hash = hash; this.next = next; } } ... }
使用WeakHashMap改造如下(只改一行代码):
public class Main { public static void main(String[] args) throws InterruptedException { // 只改变了这一句话而已~ Map<String, Object> map = new WeakHashMap<>(); for (int i = 0; i < 10000; i++) { map.put("key" + i, new byte[i]); } System.out.println(map.size()); } }
输出:
6(备注:这个值可能是10、20、100等等)
从结果中可以看出Map的size()长度不是10000了,也就是说出现了数据丢失~,但运行不会报OOM错误了。
由此可见,WeakHashMap会在系统内存紧张时使用弱引用,自动释放掉持有弱引用的内存数据。
说明:如果WeakHashMap的key都在系统内持有强引用,那么WeakHashMap就退化为普通的HashMap,因为所有的项都无法被自动清理。所以尽量不要使你的WeakHashMap里的key被强引用了,否则它将失去效果~~~
当然我们也可以自定义一个基于WeakReference或者SoftReference的缓存结构,有兴趣自定义的可以参考这个示例:java中SoftReference与WeakReference应用于高速缓存示例
为什么是弱键而不是弱值?
这个问题你应该思考。WeakHashMap它就不是为缓存而设计的,因为它是弱键而不是弱值,而一般而言值才是最占用内存空间的,所以它天生就不太适合当作缓存来用(当然当缓存来用效果比HashMap肯定还是好点的)。
WeakHashMap的经典使用场景是:保存您不控制生命周期的对象的元数据。
比如用于数据跟踪,一个简单的例子(和我以前用过的)可能是这样的:WeakHashMap<Thread, SomeMetaData>,可以跟踪你的系统中的各种线程在做什么,当线程死亡时,该条目将从您的映射中静默移除。所以你遍历就可以拿出活动线程的元数据喽。
作为缓存而言,我们是希望有个类似Map<K,SoftReference<V>>(弱值)的存储结构,那么接下来我就自己给出一个例子,可以给与你作为高速缓存来使用:
public class SoftReferenceCache<K, V> { private static final int DEFAULT_MAP_SIZE = 1024; // 初始容量 private Map<K, CacheReference<K, V>> softReferenceMap = new HashMap<>(DEFAULT_MAP_SIZE); private final ReferenceQueue<V> referenceQueue; // 方便从缓存池中清除失效的软引用 private final ValueNotFoundProvider<K, V> objectNotFoundProvider; // 当key找不到的时候的处理函数 // 若ObjectNotFoundHandler用户不指定,会采用默认的:啥都不做 public SoftReferenceCache() { this(null); } public SoftReferenceCache(ValueNotFoundProvider<K, V> handler) { this.referenceQueue = new ReferenceQueue<>(); this.objectNotFoundProvider = handler != null ? handler : (k) -> null; } public V put(K key, V value) { // 清楚无效垃圾(也会从softReferenceMap里移除) clearInvalidReference(); CacheReference<K, V> v = softReferenceMap.put(key, new CacheReference<>(key, value, referenceQueue)); if (v == null) { return null; } return v.get(); } /** * 获取,若没获取到,会使用objectNotFoundProvider提供值 */ public V get(K key) { V value = null; CacheReference<K, V> ref = softReferenceMap.get(key); if (ref == null || (ref != null && ref.get() == null)) { //软引用指向的对象被回收,并缓存该软引用 value = this.objectNotFoundProvider.provideValue(key); if (value != null) { put(key, value); } } clearInvalidReference(); // 此时调用清理 return value; } public boolean containsKey(K key) { clearInvalidReference(); return softReferenceMap.containsKey(key); } public int size() { clearInvalidReference(); return softReferenceMap.size(); } public boolean isEmpty() { return size() == 0; } public void clear() { clearInvalidReference(); softReferenceMap.clear(); } public V remove(K key) { clearInvalidReference(); CacheReference<K, V> v = softReferenceMap.remove(key); if (v == null) { return null; } return v.get(); } /** * 根据已经被回收的value对应的key,是为了清除掉map里的键值对,防止本Cache的内存泄漏 */ private void clearInvalidReference() { CacheReference<K, V> cacheReference; while ((cacheReference = (CacheReference<K, V>) referenceQueue.poll()) != null) { softReferenceMap.remove(cacheReference.getKey()); } } /** * 缓存应用类,继承软引用 * 这里把key也保存起来,是因为清理的时候也得清理键值对,否则Map可能会内存泄漏的 */ private static class CacheReference<K, V> extends SoftReference<V> { private final K key; public CacheReference(K key, V reference, ReferenceQueue<V> queue) { super(reference, queue); this.key = key; } public K getKey() { return this.key; } } /** * 若缓存里木有,就交给使用者去获取(比如查库~~~) */ @FunctionalInterface public interface ValueNotFoundProvider<K, V> { V provideValue(K k); } }
总结
其实软引用、弱引用在Android这种移动端应用,对内存更敏感的应用中使用更多些,服务端毕竟对内存敏感度差点,所以没受到很多人的重视。但是这一块个人觉得在设计高效的框架时,还是可以使用的~
Tips:谷歌不推荐使用软引用SoftReference,而建议使用弱引用WeakReference(但是我觉得通用的缓存设计用软引用,或许还更好些吧)