欢迎你来读这篇博客,这篇博客主要是关于Guava 缓存、引用类型与集合工具。 其中包括 LRU 算法、强引用、软引用、弱引用、虚引用、ReferenceQueue、手写 InMemoryCache、Guava Cache、CacheBuilder、CacheLoader、LoadingCache、驱逐策略、时间过期、RemovalListener、refresh、recordStats、CacheBuilderSpec,以及 FluentIterable、Lists、Sets、Maps、BiMap、Multimap、Table、Range、RangeSet、RangeMap、Immutable Collections、Sorted Collections 等集合工具。
序言 Guava 里最有工程价值的模块之一,就是缓存和集合工具。
如果前面几章更多是“工具类入门”,那么这一章就是 Guava 中非常贴近真实后端业务的一章。
因为后端系统里经常会遇到这些问题:
热点数据如何缓存;
缓存满了如何淘汰;
如何实现 LRU;
缓存中的对象会不会导致内存泄漏;
软引用、弱引用到底有什么区别;
Guava Cache 如何自动加载数据;
缓存过期是按写入时间还是访问时间;
缓存驱逐后如何收到通知;
缓存命中率如何统计;
一对多映射怎么优雅表达;
双向映射怎么实现;
二维表结构怎么表示;
区间匹配怎么做;
不可变集合有什么优势;
Java Stream 出现后,Guava Collections 还有哪些价值。
这一章内容很多,可以分成三个大模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 第 9 章:缓存、引用类型与集合工具 一、LRU 与引用类型 9.1 LRU 算法原理以及两种 LRU 算法的实现 9.2 SoftReference、WeakReference、PhantomReference 精讲 9.3 SoftReference 加 LRU 算法实现 InMemoryCache 二、Guava Cache 9.4 CacheLoader、CacheBuilder、LoadingCache 以及两种驱逐策略 9.5 WeakKey、SoftValues、时间逐出的两种策略 9.6 NullValue、Removal 通知、Refresh、预加载 9.7 RecordStats、CacheBuilderSpec 详解 三、Guava Collections 9.8 FluentIterable 9.9 Lists 9.10 Sets 9.11 Maps、BiMap、Multimap 9.12 Table、Range 9.13 Range、RangeMap 9.14 Immutable Collections、Sorted Collections
这篇文章不是 API 罗列,而是按“为什么需要它、怎么用、什么时候不用、和 JDK/Java Stream 怎么取舍”的方式讲。
特别注意:
Guava Cache 是非常优秀的本地缓存工具,但在现代新项目中,如果你追求更强的缓存性能和更多现代特性,也经常会看到 Caffeine。本文重点仍然围绕 Guava 课程体系展开。
正文 chapter 1:缓存到底解决什么问题 缓存的核心目的是:
用空间换时间,把高频读取、计算成本高、变化不频繁的数据暂存在更快的位置。
常见缓存场景:
用户信息缓存;
商品详情缓存;
权限信息缓存;
字典配置缓存;
热点数据缓存;
第三方接口结果缓存;
复杂计算结果缓存;
规则引擎规则缓存;
文件元数据缓存。
缓存能提升性能,但也会带来复杂度:
缓存何时过期;
缓存何时更新;
缓存满了怎么淘汰;
缓存穿透怎么办;
缓存击穿怎么办;
缓存雪崩怎么办;
缓存是否允许 null;
本地缓存和分布式缓存如何配合;
缓存数据和数据库是否一致。
Guava Cache 主要解决的是:
它不是 Redis,不是分布式缓存。
它适合:
单机热点数据;
本地字典;
本地规则;
短生命周期计算结果;
避免重复加载;
降低远程调用频率。
chapter 2:LRU 算法基本思想 LRU 是 Least Recently Used 的缩写。
意思是:
最近最少使用。
它的核心思想是:
因为一个常见假设是:
最近被访问过的数据,接下来更可能再次被访问。
例如缓存容量是 3。
依次访问:
缓存:
再次访问 A:
此时 A 是最近访问的,B 是最久未访问的。
再访问 D,缓存满了,要淘汰 B:
LRU 需要支持两个核心操作:
1 2 get(key):访问数据,并把它移动到最近使用位置。 put(key, value):写入数据,如果超过容量,淘汰最久未使用的数据。
理想时间复杂度:
要做到 O(1),通常需要:
chapter 3:LinkedHashMap 实现 LRU Java 的 LinkedHashMap 已经内置了访问顺序能力。
构造方法:
1 new LinkedHashMap <>(initialCapacity, loadFactor, accessOrder)
当 accessOrder = true 时,访问元素会调整顺序。
再重写:
就可以实现 LRU。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import java.util.LinkedHashMap;import java.util.Map;public class LinkedHashMapLruCache <K, V> extends LinkedHashMap <K, V> { private final int capacity; public LinkedHashMapLruCache (int capacity) { super (capacity, 0.75f , true ); if (capacity <= 0 ) { throw new IllegalArgumentException ("capacity must be greater than 0" ); } this .capacity = capacity; } @Override protected boolean removeEldestEntry (Map.Entry<K, V> eldest) { return size() > capacity; } }
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class LinkedHashMapLruDemo { public static void main (String[] args) { LinkedHashMapLruCache<String, String> cache = new LinkedHashMapLruCache <>(3 ); cache.put("A" , "value-A" ); cache.put("B" , "value-B" ); cache.put("C" , "value-C" ); cache.get("A" ); cache.put("D" , "value-D" ); System.out.println(cache.keySet()); } }
输出类似:
B 被淘汰了。
chapter 4:LinkedHashMap 实现 LRU 的优缺点 优点:
JDK 原生;
代码简单;
容易理解;
适合单线程或外部加锁场景;
实现 LRU 很方便。
缺点:
默认不是线程安全;
缓存能力简单;
不支持过期时间;
不支持权重;
不支持加载回源;
不支持统计;
不支持 RemovalListener;
不适合复杂缓存场景。
如果只是写一个小工具,LinkedHashMap 足够。
如果要做业务缓存,Guava Cache 更合适。
如果要做高性能现代本地缓存,可以考虑 Caffeine。
chapter 5:HashMap + 双向链表实现 LRU 手写 LRU 的经典结构:
1 HashMap<K, Node<K,V>> + 双向链表
HashMap 用于 O(1) 定位节点。
双向链表用于维护访问顺序。
链表头表示最近使用。
链表尾表示最久未使用。
结构:
1 head <-> node1 <-> node2 <-> node3 <-> tail
访问某个节点时,把它移动到头部。
缓存满时,删除尾部节点。
chapter 6:手写 LRU 节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class LruNode <K, V> { K key; V value; LruNode<K, V> prev; LruNode<K, V> next; public LruNode (K key, V value) { this .key = key; this .value = value; } }
chapter 7:手写 LRU Cache 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 import java.util.HashMap;import java.util.Map;public class ManualLruCache <K, V> { private final int capacity; private final Map<K, LruNode<K, V>> map = new HashMap <>(); private final LruNode<K, V> head = new LruNode <>(null , null ); private final LruNode<K, V> tail = new LruNode <>(null , null ); public ManualLruCache (int capacity) { if (capacity <= 0 ) { throw new IllegalArgumentException ("capacity must be greater than 0" ); } this .capacity = capacity; head.next = tail; tail.prev = head; } public V get (K key) { LruNode<K, V> node = map.get(key); if (node == null ) { return null ; } moveToHead(node); return node.value; } public void put (K key, V value) { LruNode<K, V> node = map.get(key); if (node != null ) { node.value = value; moveToHead(node); return ; } LruNode<K, V> newNode = new LruNode <>(key, value); map.put(key, newNode); addToHead(newNode); if (map.size() > capacity) { LruNode<K, V> removed = removeTail(); map.remove(removed.key); } } private void moveToHead (LruNode<K, V> node) { removeNode(node); addToHead(node); } private void addToHead (LruNode<K, V> node) { node.prev = head; node.next = head.next; head.next.prev = node; head.next = node; } private void removeNode (LruNode<K, V> node) { node.prev.next = node.next; node.next.prev = node.prev; } private LruNode<K, V> removeTail () { LruNode<K, V> node = tail.prev; removeNode(node); return node; } }
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class ManualLruCacheDemo { public static void main (String[] args) { ManualLruCache<String, String> cache = new ManualLruCache <>(2 ); cache.put("A" , "1" ); cache.put("B" , "2" ); cache.get("A" ); cache.put("C" , "3" ); System.out.println(cache.get("A" )); System.out.println(cache.get("B" )); System.out.println(cache.get("C" )); } }
输出:
B 被淘汰。
chapter 8:LRU 时间复杂度分析 使用 HashMap + 双向链表 后:
操作
时间复杂度
get
O(1)
put 已存在 key
O(1)
put 新 key
O(1)
删除尾节点
O(1)
移动节点到头部
O(1)
空间复杂度:
因为最多保存 capacity 个节点。
这也是 LRU 常见实现方式。
chapter 9:缓存淘汰策略设计 LRU 只是淘汰策略的一种。
常见策略包括:
策略
含义
FIFO
先进先出
LRU
最近最少使用
LFU
最不经常使用
TTL
按时间过期
Weight
按权重限制容量
Random
随机淘汰
Manual
手动删除
真实缓存系统通常会组合多个策略。
例如 Guava Cache 支持:
1 2 3 4 5 6 7 maximumSize maximumWeight expireAfterWrite expireAfterAccess weakKeys weakValues softValues
这比简单 LRU 更工程化。
chapter 10:Java 引用类型总览 Java 中引用类型可以分为:
1 2 3 4 强引用 软引用 SoftReference 弱引用 WeakReference 虚引用 PhantomReference
它们和 GC 的关系不同。
引用类型
GC 行为
典型场景
强引用
只要强引用存在,对象不会被回收
普通对象
软引用
内存不足时可能被回收
内存敏感缓存
弱引用
只要发生 GC,弱引用对象可能被回收
WeakHashMap、元数据关联
虚引用
不影响生命周期,只用于回收通知
堆外资源清理、对象回收跟踪
理解这些引用类型,是理解缓存和内存管理的重要基础。
chapter 11:强引用 普通对象引用就是强引用。
只要 user 还指向这个对象,GC 就不会回收它。
强引用是最常见的引用方式。
缓存中如果全部使用强引用,就会导致:
这适合明确可控的缓存。
例如:
maximumSize 控制容量;
expireAfterWrite 控制过期;
手动 invalidate。
chapter 12:SoftReference 软引用 软引用:
1 SoftReference<User> reference = new SoftReference <>(new User ());
获取对象:
1 User user = reference.get();
软引用对象在内存充足时通常不会被回收。
但在内存紧张时,GC 可能回收它。
软引用常被用于“内存敏感缓存”。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 import java.lang.ref.SoftReference;public class SoftReferenceDemo { public static void main (String[] args) { SoftReference<byte []> reference = new SoftReference <>(new byte [1024 * 1024 ]); byte [] data = reference.get(); System.out.println(data != null ); } }
软引用不是稳定缓存。
它只是告诉 JVM:
chapter 13:WeakReference 弱引用 弱引用:
1 WeakReference<User> reference = new WeakReference <>(new User ());
弱引用对象只要没有强引用,下一次 GC 就可能被回收。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.lang.ref.WeakReference;public class WeakReferenceDemo { public static void main (String[] args) { WeakReference<Object> reference = new WeakReference <>(new Object ()); System.out.println(reference.get() != null ); System.gc(); System.out.println(reference.get() != null ); } }
第二次可能输出 false。
弱引用常见场景:
WeakHashMap;
缓存 key 不应该阻止对象回收;
框架内部元数据关联;
监听器弱引用,避免内存泄漏。
chapter 14:PhantomReference 虚引用 虚引用最特殊。
它的 get() 永远返回 null。
1 PhantomReference<Object> reference = new PhantomReference <>(object, referenceQueue);
虚引用必须配合 ReferenceQueue 使用。
它用于:
在对象被回收时收到通知。
常见场景:
堆外内存清理;
资源释放跟踪;
对象生命周期监控;
更底层的清理机制。
业务缓存中很少直接使用虚引用。
它更多用于框架和底层资源管理。
chapter 15:ReferenceQueue ReferenceQueue 用于接收被 GC 回收的引用对象。
例如软引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import java.lang.ref.ReferenceQueue;import java.lang.ref.SoftReference;public class ReferenceQueueDemo { public static void main (String[] args) { ReferenceQueue<Object> queue = new ReferenceQueue <>(); Object value = new Object (); SoftReference<Object> reference = new SoftReference <>(value, queue); value = null ; System.gc(); Object polled = queue.poll(); System.out.println(polled); } }
当引用对象被回收后,引用本身可能进入队列。
缓存可以通过 ReferenceQueue 清理已经失效的引用记录。
chapter 16:缓存场景中的引用选择 1. 强引用缓存 优点:
缺点:
适合:
1 Guava Cache maximumSize + expireAfterWrite
2. 软引用缓存 优点:
缺点:
回收时机不可控;
命中率不稳定;
现代 JVM 下不建议作为核心缓存策略。
适合:
3. 弱引用缓存 优点:
缺点:
适合:
4. 虚引用 不适合普通缓存。
适合底层资源清理。
chapter 17:SoftReference + LRU 实现 InMemoryCache 我们设计一个简单内存缓存:
需求:
使用 LRU 控制最大条目数;
value 使用 SoftReference;
使用 ReferenceQueue 清理被 GC 回收的 value;
支持 get、put、remove、size。
注意:
这是学习用实现,不建议直接用于生产核心缓存。
生产建议使用 Guava Cache 或 Caffeine。
chapter 18:SoftValue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.lang.ref.ReferenceQueue;import java.lang.ref.SoftReference;public class SoftValue <K, V> extends SoftReference <V> { private final K key; public SoftValue (K key, V value, ReferenceQueue<V> queue) { super (value, queue); this .key = key; } public K getKey () { return key; } }
这里把 key 保存到 SoftValue 中。
当 value 被 GC 回收后,我们可以从 ReferenceQueue 中拿到 SoftValue,再根据 key 清理 Map。
chapter 19:InMemoryCache 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 import java.lang.ref.ReferenceQueue;import java.util.LinkedHashMap;import java.util.Map;public class InMemoryCache <K, V> { private final int capacity; private final ReferenceQueue<V> referenceQueue = new ReferenceQueue <>(); private final Map<K, SoftValue<K, V>> cache; public InMemoryCache (int capacity) { if (capacity <= 0 ) { throw new IllegalArgumentException ("capacity must be greater than 0" ); } this .capacity = capacity; this .cache = new LinkedHashMap <>(capacity, 0.75f , true ) { @Override protected boolean removeEldestEntry (Map.Entry<K, SoftValue<K, V>> eldest) { return size() > InMemoryCache.this .capacity; } }; } public synchronized void put (K key, V value) { cleanUpClearedReferences(); cache.put(key, new SoftValue <>(key, value, referenceQueue)); } public synchronized V get (K key) { cleanUpClearedReferences(); SoftValue<K, V> reference = cache.get(key); if (reference == null ) { return null ; } V value = reference.get(); if (value == null ) { cache.remove(key); } return value; } public synchronized void remove (K key) { cache.remove(key); } public synchronized int size () { cleanUpClearedReferences(); return cache.size(); } @SuppressWarnings("unchecked") private void cleanUpClearedReferences () { SoftValue<K, V> reference; while ((reference = (SoftValue<K, V>) referenceQueue.poll()) != null ) { cache.remove(reference.getKey()); } } }
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class InMemoryCacheDemo { public static void main (String[] args) { InMemoryCache<String, String> cache = new InMemoryCache <>(2 ); cache.put("A" , "value-A" ); cache.put("B" , "value-B" ); cache.get("A" ); cache.put("C" , "value-C" ); System.out.println(cache.get("A" )); System.out.println(cache.get("B" )); System.out.println(cache.get("C" )); } }
chapter 20:手写缓存的问题 手写缓存看起来不难,但生产级缓存很复杂。
你还需要考虑:
并发安全;
加载回源;
缓存击穿;
缓存穿透;
过期策略;
驱逐策略;
统计信息;
删除通知;
异步刷新;
权重;
内存占用;
清理线程;
异常处理。
所以:
手写缓存适合理解原理,不适合替代成熟缓存库。
chapter 21:Guava Cache 是什么 Guava Cache 是 Guava 提供的本地缓存工具。
核心类:
1 2 3 4 CacheBuilder CacheLoader LoadingCache Cache
它支持:
自动加载;
最大容量;
最大权重;
写入后过期;
访问后过期;
弱 key;
弱 value;
软 value;
删除通知;
统计信息;
刷新;
预加载;
手动失效。
它适合单 JVM 本地缓存。
chapter 22:CacheBuilder 基本使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import com.google.common.cache.Cache;import com.google.common.cache.CacheBuilder;import java.util.concurrent.TimeUnit;public class CacheBuilderDemo { public static void main (String[] args) throws Exception { Cache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(100 ) .expireAfterWrite(10 , TimeUnit.MINUTES) .build(); cache.put("name" , "Mario" ); System.out.println(cache.getIfPresent("name" )); } }
Cache 不会自动加载数据。
如果 key 不存在:
返回 null。
chapter 23:CacheLoader 和 LoadingCache LoadingCache 可以自动加载数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import com.google.common.cache.CacheBuilder;import com.google.common.cache.CacheLoader;import com.google.common.cache.LoadingCache;import java.util.concurrent.TimeUnit;public class LoadingCacheDemo { public static void main (String[] args) throws Exception { LoadingCache<Long, String> userCache = CacheBuilder.newBuilder() .maximumSize(1000 ) .expireAfterWrite(10 , TimeUnit.MINUTES) .build(new CacheLoader <Long, String>() { @Override public String load (Long userId) { return loadUserName(userId); } }); System.out.println(userCache.get(1L )); System.out.println(userCache.get(1L )); } private static String loadUserName (Long userId) { System.out.println("加载用户,userId = " + userId); return "user-" + userId; } }
第一次 get 会调用 load。
第二次 get 会命中缓存。
chapter 24:get 和 getUnchecked LoadingCache 常用方法:
1 2 cache.get(key) cache.getUnchecked(key)
get 会抛出 checked exception 包装异常:
getUnchecked 会把异常包装成 unchecked exception。
示例:
1 2 3 4 5 try { String value = cache.get(key); } catch (ExecutionException e) { throw new RuntimeException ("load cache failed" , e); }
如果你确定 load 不会抛 checked exception,可以用:
实践建议:
业务代码希望明确处理加载失败,用 get;
简单场景或不想处理 checked exception,用 getUnchecked;
load 中不要吞异常。
chapter 25:maximumSize 容量驱逐 1 2 3 LoadingCache<Long, String> cache = CacheBuilder.newBuilder() .maximumSize(1000 ) .build(loader);
maximumSize 表示最多缓存多少条记录。
超过后会进行驱逐。
Guava Cache 的驱逐不是严格实时、精确每次立即发生,而是近似维护。
这对性能有好处。
适合绝大多数业务缓存。
chapter 26:maximumWeight 权重驱逐 如果每个缓存项大小差异很大,只按条目数量不合理。
例如:
有的 value 很小;
有的 value 很大;
图片、报表、JSON 大小不同。
可以使用权重:
1 2 3 4 5 6 7 8 9 10 11 12 import com.google.common.cache.CacheBuilder;import com.google.common.cache.Weigher; LoadingCache<String, String> cache = CacheBuilder.newBuilder() .maximumWeight(10000 ) .weigher(new Weigher <String, String>() { @Override public int weigh (String key, String value) { return value.length(); } }) .build(loader);
maximumWeight 必须配合 weigher 使用。
注意:
权重是 int;
权重计算要快;
不要在 weigh 中做复杂逻辑;
权重不是精确内存大小,只是业务估算值。
chapter 27:weakKeys weakKeys() 表示缓存 key 使用弱引用。
1 2 3 CacheBuilder.newBuilder() .weakKeys() .build();
如果 key 没有其他强引用,GC 后缓存项可能失效。
适合:
key 是对象实例;
不希望缓存阻止 key 被回收;
框架元数据关联。
不适合:
业务 ID key;
String key;
Long key;
需要稳定缓存的场景。
因为弱 key 可能随 GC 消失。
chapter 28:weakValues 和 softValues weakValues():
1 2 3 CacheBuilder.newBuilder() .weakValues() .build();
value 使用弱引用。
没有强引用时,GC 后可能回收。
softValues():
1 2 3 CacheBuilder.newBuilder() .softValues() .build();
value 使用软引用。
内存紧张时可能回收。
实践建议:
不要随便使用 weakValues;
softValues 在现代缓存设计中也要谨慎;
大多数业务缓存优先使用 maximumSize、expireAfterWrite;
引用型缓存适合特殊内存敏感场景。
chapter 29:expireAfterWrite 写入后一段时间过期。
1 2 3 CacheBuilder.newBuilder() .expireAfterWrite(10 , TimeUnit.MINUTES) .build();
含义:
适合:
数据有固定有效期;
配置定期刷新;
第三方接口结果短时间缓存;
用户信息允许延迟刷新。
chapter 30:expireAfterAccess 访问后一段时间过期。
1 2 3 CacheBuilder.newBuilder() .expireAfterAccess(10 , TimeUnit.MINUTES) .build();
含义:
适合:
热点数据保留;
冷数据淘汰;
会话类缓存;
用户上下文缓存。
对比:
策略
含义
expireAfterWrite
写入后固定时间过期
expireAfterAccess
最后访问后固定时间过期
chapter 31:Guava Cache 不允许缓存 null CacheLoader.load() 不能返回 null。
如果返回 null,会抛异常。
为什么?
因为缓存里 null 语义不清楚:
1 2 3 4 是 key 不存在? 还是 value 就是 null? 是加载失败? 还是查不到数据?
Guava 选择禁止 null,避免歧义。
如果需要表达空值,可以使用:
Optional;
Null Object;
自定义 NullValue。
chapter 32:NullValue 处理方案 定义 NullValue:
1 2 3 4 public enum NullValue { INSTANCE }
缓存类型:
1 2 3 4 5 6 7 8 9 10 LoadingCache<Long, Object> cache = CacheBuilder.newBuilder() .maximumSize(1000 ) .build(new CacheLoader <Long, Object>() { @Override public Object load (Long key) { User user = findUser(key); return user == null ? NullValue.INSTANCE : user; } });
读取:
1 2 3 4 5 6 7 Object value = cache.getUnchecked(userId);if (value == NullValue.INSTANCE) { return null ; }return (User) value;
更现代的写法可以缓存:
例如:
1 LoadingCache<Long, Optional<User>> cache
chapter 33:RemovalListener 缓存项被移除时,可以收到通知。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import com.google.common.cache.*; LoadingCache<Long, String> cache = CacheBuilder.newBuilder() .maximumSize(100 ) .removalListener(new RemovalListener <Long, String>() { @Override public void onRemoval (RemovalNotification<Long, String> notification) { System.out.println("缓存移除,key = " + notification.getKey() + ",cause = " + notification.getCause()); } }) .build(loader);
chapter 34:RemovalCause 常见 RemovalCause:
原因
说明
EXPLICIT
手动移除
REPLACED
被新值替换
COLLECTED
被 GC 回收
EXPIRED
过期
SIZE
超过容量或权重被驱逐
示例:
可以用于:
日志记录;
监控驱逐原因;
释放外部资源;
缓存调优。
注意:
RemovalListener 不要执行太重逻辑,避免影响缓存操作。
chapter 35:refreshAfterWrite refreshAfterWrite 表示写入后一段时间允许刷新。
1 2 3 LoadingCache<Long, String> cache = CacheBuilder.newBuilder() .refreshAfterWrite(5 , TimeUnit.MINUTES) .build(loader);
它和 expire 不同。
expire 是过期后重新加载。
refresh 是旧值仍然可用,同时触发刷新。
适合:
缓存希望后台刷新;
不希望请求直接等待加载;
数据允许短暂旧值。
需要注意:
默认 reload 可能同步执行;
可以重写 reload 实现异步刷新;
refresh 不等于定时主动刷新所有 key。
chapter 36:reload 1 2 3 4 5 6 7 8 9 10 11 12 13 LoadingCache<Long, String> cache = CacheBuilder.newBuilder() .refreshAfterWrite(5 , TimeUnit.MINUTES) .build(new CacheLoader <Long, String>() { @Override public String load (Long key) { return loadValue(key); } @Override public ListenableFuture<String> reload (Long key, String oldValue) { return Futures.immediateFuture(loadValue(key)); } });
如果要异步 reload,需要配合线程池。
在实际项目中,要小心:
reload 异常;
下游压力;
刷新风暴;
key 数量过多;
旧值是否可接受。
chapter 37:预加载缓存 可以用:
1 2 cache.put(key, value) cache.putAll(map)
示例:
1 2 3 4 5 6 Cache<Long, String> cache = CacheBuilder.newBuilder() .maximumSize(1000 ) .build(); cache.put(1L , "Mario" ); cache.put(2L , "Luigi" );
适合:
启动加载字典;
预热热点数据;
定时刷新配置;
灰度前预加载。
但启动时预加载太多数据会拖慢启动速度。
要控制规模。
chapter 38:缓存穿透处理思路 缓存穿透是指:
请求的数据缓存没有,数据库也没有,导致每次请求都打到数据库。
处理思路:
1. 缓存空值 用 Optional 或 NullValue 缓存不存在结果。
2. 参数校验 非法 ID 直接拒绝。
3. 布隆过滤器 在访问缓存和数据库前判断 key 是否可能存在。
4. 限流 对异常 key 请求限速。
Guava Cache 可以缓存 Optional:
1 LoadingCache<Long, Optional<User>> cache
查不到用户时返回:
chapter 39:recordStats 开启统计:
1 2 3 4 LoadingCache<Long, String> cache = CacheBuilder.newBuilder() .maximumSize(1000 ) .recordStats() .build(loader);
获取统计:
1 CacheStats stats = cache.stats();
常见指标:
1 2 3 4 5 6 7 8 9 stats.hitCount() stats.missCount() stats.hitRate() stats.missRate() stats.loadSuccessCount() stats.loadExceptionCount() stats.totalLoadTime() stats.averageLoadPenalty() stats.evictionCount()
chapter 40:CacheStats 指标解释
指标
含义
hitCount
命中次数
missCount
未命中次数
hitRate
命中率
missRate
未命中率
loadSuccessCount
加载成功次数
loadExceptionCount
加载异常次数
totalLoadTime
总加载耗时
averageLoadPenalty
平均加载耗时
evictionCount
驱逐次数
这些指标可以帮助判断缓存效果。
例如:
命中率低,说明缓存价值不高或过期太快;
驱逐次数高,说明容量可能太小;
加载耗时高,说明回源慢;
加载异常多,说明下游不稳定。
chapter 41:CacheBuilderSpec CacheBuilderSpec 可以用字符串配置 CacheBuilder。
示例:
1 2 3 4 5 6 7 8 import com.google.common.cache.CacheBuilder;import com.google.common.cache.CacheBuilderSpec;CacheBuilderSpec spec = CacheBuilderSpec.parse( "maximumSize=1000,expireAfterWrite=10m,recordStats" ); CacheBuilder<Object, Object> builder = CacheBuilder.from(spec);
适合配置化:
1 2 cache: user: maximumSize=1000,expireAfterWrite=10m,recordStats
然后在代码里解析。
好处:
缓存策略可配置;
不用改代码;
便于不同环境设置不同策略。
缺点:
字符串配置容易写错;
IDE 不容易提示;
配置项要做好校验。
chapter 42:Guava Cache 实战:用户缓存服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import com.google.common.cache.*;import java.util.Optional;import java.util.concurrent.TimeUnit;public class UserCacheService { private final LoadingCache<Long, Optional<User>> cache; public UserCacheService () { this .cache = CacheBuilder.newBuilder() .maximumSize(1000 ) .expireAfterWrite(10 , TimeUnit.MINUTES) .recordStats() .removalListener(new RemovalListener <Long, Optional<User>>() { @Override public void onRemoval (RemovalNotification<Long, Optional<User>> notification) { System.out.println("用户缓存移除,key = " + notification.getKey() + ",cause = " + notification.getCause()); } }) .build(new CacheLoader <Long, Optional<User>>() { @Override public Optional<User> load (Long userId) { return Optional.ofNullable(loadUserFromDb(userId)); } }); } public Optional<User> getUser (Long userId) { return cache.getUnchecked(userId); } public void invalidate (Long userId) { cache.invalidate(userId); } public CacheStats stats () { return cache.stats(); } private User loadUserFromDb (Long userId) { System.out.println("查询数据库,userId = " + userId); if (userId <= 0 ) { return null ; } return new User (userId, "user-" + userId, true ); } }
这个示例包含:
自动加载;
Optional 缓存空值;
最大容量;
写入过期;
删除通知;
统计信息。
chapter 43:FluentIterable 是什么 FluentIterable 是 Guava 早期提供的链式集合处理工具。
在 Java 8 Stream 出现之前,它非常有用。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import com.google.common.base.Predicate;import com.google.common.base.Function;import com.google.common.collect.FluentIterable;import java.util.List;public class FluentIterableDemo { public static void main (String[] args) { List<User> users = List.of( new User (1L , "Mario" , true ), new User (2L , "Luigi" , true ), new User (3L , "Peach" , false ) ); List<String> names = FluentIterable.from(users) .filter(new Predicate <User>() { @Override public boolean apply (User user) { return user.isEnabled(); } }) .transform(new Function <User, String>() { @Override public String apply (User user) { return user.getUsername(); } }) .toList(); System.out.println(names); } }
Java 8 之后,更推荐:
1 2 3 4 List<String> names = users.stream() .filter(User::isEnabled) .map(User::getUsername) .toList();
chapter 44:FluentIterable 与 Stream 对比
对比项
FluentIterable
Java Stream
出现时代
Java 8 之前
Java 8
filter
支持
支持
transform/map
支持
支持
first/last
支持
Stream 可实现
生态
Guava
JDK 标准
推荐程度
老项目维护
新项目优先 Stream
如果维护老项目,要能看懂 FluentIterable。
新项目优先 Stream。
chapter 45:Lists 工具类 Guava Lists 常用方法:
1 2 3 4 5 6 Lists.newArrayList() Lists.partition(list, size) Lists.reverse(list) Lists.transform(list, function) Lists.cartesianProduct(...) Lists.charactersOf(string)
newArrayList 1 2 3 4 5 6 7 8 9 10 11 12 import com.google.common.collect.Lists;import java.util.ArrayList;public class ListsDemo { public static void main (String[] args) { ArrayList<String> list = Lists.newArrayList("A" , "B" , "C" ); System.out.println(list); } }
Java 9+ 有 List.of,但它返回不可变列表。
如果需要可变 ArrayList,Lists.newArrayList 仍然很方便。
chapter 46:Lists.partition 分批处理非常常见。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import com.google.common.collect.Lists;import java.util.List;public class ListsPartitionDemo { public static void main (String[] args) { List<Integer> ids = List.of(1 , 2 , 3 , 4 , 5 , 6 , 7 ); List<List<Integer>> partitions = Lists.partition(ids, 3 ); System.out.println(partitions); } }
输出:
1 [[1, 2, 3], [4, 5, 6], [7]]
适合:
批量查询;
批量插入;
分批调用接口;
分批发送消息。
reverse 1 List<Integer> reversed = Lists.reverse(ids);
注意返回的是视图,不一定是新列表。
1 List<String> names = Lists.transform(users, User::getUsername);
Java 8 后推荐 Stream。
cartesianProduct 笛卡尔积:
1 2 3 4 List<List<String>> result = Lists.cartesianProduct( List.of("S" , "M" ), List.of("Red" , "Blue" ) );
输出:
1 [[S, Red], [S, Blue], [M, Red], [M, Blue]]
适合 SKU 组合。
charactersOf 1 List<Character> chars = Lists.charactersOf("abc" );
chapter 48:Sets 工具类 Guava Sets 常用能力:
1 2 3 4 5 6 7 Sets.newHashSet() Sets.union(a, b) Sets.intersection(a, b) Sets.difference(a, b) Sets.symmetricDifference(a, b) Sets.combinations(set, size) Sets.powerSet(set)
chapter 49:Sets 集合运算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import com.google.common.collect.Sets;import java.util.Set;public class SetsOperationDemo { public static void main (String[] args) { Set<String> a = Set.of("A" , "B" , "C" ); Set<String> b = Set.of("B" , "C" , "D" ); System.out.println(Sets.union(a, b)); System.out.println(Sets.intersection(a, b)); System.out.println(Sets.difference(a, b)); System.out.println(Sets.symmetricDifference(a, b)); } }
含义:
方法
含义
union
并集
intersection
交集
difference
差集
symmetricDifference
对称差集
常见业务:
权限对比;
标签差异;
用户分组差异;
数据同步差异;
新增/删除集合计算。
chapter 50:Sets.combinations 和 powerSet 组合:
1 Set<Set<String>> combinations = Sets.combinations(Set.of("A" , "B" , "C" ), 2 );
结果:
幂集:
1 Set<Set<String>> powerSet = Sets.powerSet(Set.of("A" , "B" , "C" ));
幂集会生成所有子集。
注意:
powerSet 数据量是 2^n,n 大时非常危险。
不要对大集合使用 powerSet。
chapter 51:Maps 工具类 Guava Maps 常用能力:
1 2 3 4 5 6 Maps.newHashMap() Maps.uniqueIndex(...) Maps.difference(...) Maps.transformValues(...) Maps.filterKeys(...) Maps.filterValues(...)
chapter 52:Maps.uniqueIndex 根据对象字段建立索引。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import com.google.common.collect.Maps;import java.util.List;import java.util.Map;public class UniqueIndexDemo { public static void main (String[] args) { List<User> users = List.of( new User (1L , "Mario" , true ), new User (2L , "Luigi" , true ) ); Map<Long, User> userMap = Maps.uniqueIndex(users, User::getId); System.out.println(userMap); } }
注意:
key 必须唯一,否则会抛异常。
适合:
List 转 Map;
按 ID 建索引;
批量查询结果映射;
后续快速查找。
Java Stream 也可以:
1 users.stream().collect(Collectors.toMap(User::getId, Function.identity()))
chapter 53:Maps.difference 比较两个 Map 的差异。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import com.google.common.collect.MapDifference;import com.google.common.collect.Maps;import java.util.Map;public class MapsDifferenceDemo { public static void main (String[] args) { Map<String, String> oldMap = Map.of( "name" , "Mario" , "city" , "Hangzhou" ); Map<String, String> newMap = Map.of( "name" , "Mario" , "city" , "Shanghai" , "level" , "VIP" ); MapDifference<String, String> difference = Maps.difference(oldMap, newMap); System.out.println("只在左边:" + difference.entriesOnlyOnLeft()); System.out.println("只在右边:" + difference.entriesOnlyOnRight()); System.out.println("相同:" + difference.entriesInCommon()); System.out.println("不同:" + difference.entriesDiffering()); } }
适合:
配置变更对比;
表单修改对比;
审计日志;
数据同步差异。
chapter 54:BiMap 双向映射 BiMap 表示双向唯一映射。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import com.google.common.collect.BiMap;import com.google.common.collect.HashBiMap;public class BiMapDemo { public static void main (String[] args) { BiMap<String, Integer> statusMap = HashBiMap.create(); statusMap.put("CREATED" , 1 ); statusMap.put("PAID" , 2 ); System.out.println(statusMap.get("PAID" )); BiMap<Integer, String> inverse = statusMap.inverse(); System.out.println(inverse.get(2 )); } }
注意:
BiMap 的 value 也必须唯一。
适合:
chapter 55:Multimap 一键多值 普通 Map:
写起来很啰嗦。
Guava 提供 Multimap。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import com.google.common.collect.ArrayListMultimap;import com.google.common.collect.Multimap;public class MultimapDemo { public static void main (String[] args) { Multimap<String, String> roleUsers = ArrayListMultimap.create(); roleUsers.put("admin" , "Mario" ); roleUsers.put("admin" , "Luigi" ); roleUsers.put("user" , "Peach" ); System.out.println(roleUsers.get("admin" )); } }
输出:
常见实现:
实现
特点
ArrayListMultimap
value 可重复,有序
HashMultimap
value 去重,无序
LinkedListMultimap
保持插入顺序
TreeMultimap
key/value 排序
chapter 56:Multimap 实战:用户按角色分组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import com.google.common.collect.ArrayListMultimap;import com.google.common.collect.Multimap;import java.util.List;public class UserRoleGroupDemo { public static void main (String[] args) { List<UserRole> userRoles = List.of( new UserRole ("Mario" , "admin" ), new UserRole ("Luigi" , "admin" ), new UserRole ("Peach" , "user" ) ); Multimap<String, String> roleUserMap = ArrayListMultimap.create(); for (UserRole userRole : userRoles) { roleUserMap.put(userRole.getRole(), userRole.getUsername()); } System.out.println(roleUserMap.get("admin" )); } }
辅助类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class UserRole { private final String username; private final String role; public UserRole (String username, String role) { this .username = username; this .role = role; } public String getUsername () { return username; } public String getRole () { return role; } }
Java Stream 也可以分组:
1 Collectors.groupingBy(...)
但 Multimap 在某些需要频繁 put 的场景很方便。
chapter 57:Table 二维表结构 Table<R, C, V> 表示二维表。
类似:
1 rowKey + columnKey -> value
例如学生成绩:
1 student + subject -> score
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import com.google.common.collect.HashBasedTable;import com.google.common.collect.Table;public class TableDemo { public static void main (String[] args) { Table<String, String, Integer> scoreTable = HashBasedTable.create(); scoreTable.put("Mario" , "Math" , 90 ); scoreTable.put("Mario" , "English" , 85 ); scoreTable.put("Luigi" , "Math" , 88 ); System.out.println(scoreTable.get("Mario" , "Math" )); System.out.println(scoreTable.row("Mario" )); System.out.println(scoreTable.column("Math" )); System.out.println(scoreTable.cellSet()); } }
适合:
二维配置;
价格矩阵;
学生成绩;
地区-品类指标;
行列报表。
chapter 58:Range 区间表达 Range 用于表达区间。
常见方法:
1 2 3 4 5 6 Range.closed(1 , 10 ) Range.open(1 , 10 ) Range.closedOpen(1 , 10 ) Range.openClosed(1 , 10 ) Range.atLeast(1 ) Range.lessThan(10 )
示例:
1 2 3 4 5 6 7 8 9 10 11 import com.google.common.collect.Range;public class RangeDemo { public static void main (String[] args) { Range<Integer> range = Range.closedOpen(1 , 10 ); System.out.println(range.contains(1 )); System.out.println(range.contains(10 )); } }
输出:
chapter 59:RangeSet 区间集合 RangeSet 表示多个区间的集合,并且会自动合并相连或重叠区间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import com.google.common.collect.Range;import com.google.common.collect.RangeSet;import com.google.common.collect.TreeRangeSet;public class RangeSetDemo { public static void main (String[] args) { RangeSet<Integer> rangeSet = TreeRangeSet.create(); rangeSet.add(Range.closed(1 , 10 )); rangeSet.add(Range.closed(8 , 20 )); System.out.println(rangeSet); System.out.println(rangeSet.contains(15 )); } }
输出:
适合:
时间段合并;
IP 段;
价格区间;
年龄区间;
权限范围。
chapter 60:RangeMap 区间映射 RangeMap 表示:
例如会员等级:
1 2 3 [0,100) -> BRONZE [100,500) -> SILVER [500,+∞) -> GOLD
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import com.google.common.collect.Range;import com.google.common.collect.RangeMap;import com.google.common.collect.TreeRangeMap;public class RangeMapDemo { public static void main (String[] args) { RangeMap<Integer, String> levelMap = TreeRangeMap.create(); levelMap.put(Range.closedOpen(0 , 100 ), "BRONZE" ); levelMap.put(Range.closedOpen(100 , 500 ), "SILVER" ); levelMap.put(Range.atLeast(500 ), "GOLD" ); System.out.println(levelMap.get(50 )); System.out.println(levelMap.get(200 )); System.out.println(levelMap.get(800 )); } }
输出:
chapter 61:RangeMap 实战:价格区间匹配 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import com.google.common.collect.Range;import com.google.common.collect.RangeMap;import com.google.common.collect.TreeRangeMap;import java.math.BigDecimal;public class PriceRangeDiscountDemo { public static void main (String[] args) { RangeMap<BigDecimal, String> discountMap = TreeRangeMap.create(); discountMap.put(Range.closedOpen(new BigDecimal ("0" ), new BigDecimal ("100" )), "NO_DISCOUNT" ); discountMap.put(Range.closedOpen(new BigDecimal ("100" ), new BigDecimal ("500" )), "DISCOUNT_10" ); discountMap.put(Range.atLeast(new BigDecimal ("500" )), "DISCOUNT_50" ); System.out.println(discountMap.get(new BigDecimal ("80" ))); System.out.println(discountMap.get(new BigDecimal ("300" ))); System.out.println(discountMap.get(new BigDecimal ("800" ))); } }
适合:
价格区间;
时间区间;
积分等级;
会员等级;
风险评分;
订单金额分层。
chapter 62:Immutable Collections Guava 的不可变集合非常经典。
常见类型:
1 2 3 4 5 ImmutableList ImmutableSet ImmutableMap ImmutableMultimap ImmutableTable
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import com.google.common.collect.ImmutableList;import com.google.common.collect.ImmutableMap;public class ImmutableCollectionsDemo { public static void main (String[] args) { ImmutableList<String> names = ImmutableList.of("Mario" , "Luigi" , "Peach" ); ImmutableMap<String, Integer> statusMap = ImmutableMap.of( "CREATED" , 1 , "PAID" , 2 ); System.out.println(names); System.out.println(statusMap); } }
不可变集合创建后不能修改。
如果调用:
会抛异常。
chapter 63:不可变集合的优势 1. 线程安全 不可变对象天然线程安全。
多个线程读不需要加锁。
2. 防御性复制 避免外部修改内部集合。
1 this .roles = ImmutableList.copyOf(roles);
3. 表达语义明确 表示这个集合不应该被修改。
4. 减少 bug 避免方法拿到集合后偷偷修改。
5. 适合常量配置 例如状态码、权限码、白名单。
chapter 64:ImmutableList.copyOf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import com.google.common.collect.ImmutableList;import java.util.List;public class ImmutableCopyDemo { public static void main (String[] args) { List<String> mutable = List.of("A" , "B" , "C" ); ImmutableList<String> immutable = ImmutableList.copyOf(mutable); System.out.println(immutable); } }
Java 9+ 也有:
1 2 3 List.of(...) Map.of(...) Set.of(...)
但 Guava 的 Immutable Collections 仍然提供了更丰富的不可变集合类型,例如 ImmutableMultimap、ImmutableTable。
chapter 65:Sorted Collections 和 Ordering Guava 提供 Ordering 作为早期比较器工具。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import com.google.common.collect.Ordering;import java.util.List;public class OrderingDemo { public static void main (String[] args) { List<Integer> numbers = List.of(3 , 1 , 2 ); List<Integer> sorted = Ordering.natural().sortedCopy(numbers); System.out.println(sorted); } }
Java 8 后,更多使用:
1 2 Comparator Stream.sorted()
例如:
1 numbers.stream().sorted().toList()
Guava 的 Ordering 在老项目中常见。
新项目优先 JDK Comparator。
chapter 66:不可变集合与线程安全 不可变集合的线程安全来自:
如果集合内部元素本身也是不可变对象,那么整体非常安全。
但要注意:
如果 User 是可变对象,集合结构不可变,不代表 User 内部不可变。
例如:
1 2 不能 add/remove User 但 User 自己的字段可能被改
所以真正线程安全要看:
集合是否不可变;
元素是否不可变;
是否存在外部共享可变状态。
chapter 67:Guava Collections 和 Java Stream 怎么选
场景
推荐
过滤转换
Java Stream
分批 partition
Guava Lists.partition
集合交并差
Guava Sets
一键多值
Guava Multimap
双向映射
Guava BiMap
二维表
Guava Table
区间表达
Guava Range / RangeSet / RangeMap
不可变多值集合
Guava ImmutableMultimap
普通不可变 List/Map
Java List.of / Map.of 或 Guava
排序
Java Comparator / Stream
Guava Collections 的优势在于:
它提供了很多 JDK 没有的集合结构。
比如:
1 2 3 4 5 6 BiMap Multimap Table RangeSet RangeMap ImmutableMultimap
这些在业务建模中很有价值。
chapter 68:第九章使用建议 1. 简单 LRU 用 LinkedHashMap 学习和小工具足够。
2. 业务缓存用 Guava Cache 或更现代缓存库 不要手写复杂缓存用于生产核心链路。
3. 引用类型缓存要谨慎 SoftReference、WeakReference 不适合大多数稳定业务缓存。
4. Guava Cache 不允许 null 用 Optional 或 NullValue 表达空值。
5. 缓存必须监控 开启:
观察命中率和驱逐情况。
6. 本地缓存不是分布式缓存 多实例部署要注意一致性。
7. 集合工具要按场景选 Java Stream 很强,但不是所有集合建模问题都适合 Stream。
8. 不可变集合非常推荐 尤其适合配置、常量、DTO 防御性复制。
9. RangeMap 很适合区间匹配 比一堆 if else 更清晰。
10. Multimap 很适合一键多值 比 Map<K, List<V>> 更优雅。
chapter 69:一句话总结 这一章的核心是:
缓存解决的是高频读取和重复计算问题,引用类型影响对象生命周期,Guava Cache 提供了成熟的单机缓存能力,Guava Collections 提供了大量 JDK 没有的高级集合结构。
LRU 的核心是:
引用类型的核心是:
Guava Cache 的核心是:
1 用 CacheBuilder 声明缓存策略,用 CacheLoader 加载数据,用 LoadingCache 统一访问缓存。
Guava Collections 的核心是:
例如:
Multimap 表达一键多值;
BiMap 表达双向唯一映射;
Table 表达二维表;
RangeMap 表达区间到值;
ImmutableList 表达不可变集合。
好代码不是把所有数据都塞进 Map<String, Object>。
好代码应该用合适的数据结构表达业务语义。
缓存也是一样。
好缓存不是随便加一层 Map。
好缓存要考虑:
1 2 3 4 5 6 7 8 9 10 容量 过期 驱逐 加载 空值 刷新 统计 并发 一致性 可观测性
这才是缓存真正的工程难点。
参考资料
Google Guava API: CacheBuilder.
Google Guava API: CacheLoader.
Google Guava API: LoadingCache.
Google Guava API: CacheStats.
Google Guava API: RemovalListener.
Google Guava API: RemovalCause.
Google Guava API: FluentIterable.
Google Guava API: Lists.
Google Guava API: Sets.
Google Guava API: Maps.
Google Guava API: BiMap.
Google Guava API: Multimap.
Google Guava API: Table.
Google Guava API: Range.
Google Guava API: RangeSet.
Google Guava API: RangeMap.
Google Guava API: Immutable Collections.
Java Documentation: Reference.
Java Documentation: SoftReference.
Java Documentation: WeakReference.
Java Documentation: PhantomReference.
Java Documentation: ReferenceQueue.
Joshua Bloch. Effective Java .
启示录 富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。