基本工具
使用和避免null
很多Guava工具类对Null值都采用快速失败操作,Null的含糊语义让人很不舒服,Null很少可以明确地表示某种语义。
Guava用Optional<T>表示可能为null的T类型引用。一个Optional实例可能包含非null的引用(我们称之为引用存在),也可能什么也不包括(称之为引用缺失)。它从不说包含的是null值,而是用存在或缺失来表示。但Optional从不会包含null值引用。
创建Optional实例(以下都是静态方法):
Optional.of(T) | 创建指定引用的Optional实例,若引用为null则快速失败 |
创建引用缺失的Optional实例 | |
Optional.fromNullable(T) | 创建指定引用的Optional实例,若引用为null则表示缺失 |
用Optional实例查询引用(以下都是非静态方法):
如果Optional包含非null的引用(引用存在),返回true | |
T get() | 返回Optional所包含的引用,若引用缺失,则抛出java.lang.IllegalStateException |
T or(T) | 返回Optional所包含的引用,若引用缺失,返回指定的值 |
T orNull() | 返回Optional所包含的引用,若引用缺失,返回null |
Set<T> asSet() | 返回Optional所包含引用的单例不可变集,如果引用存在,返回一个只有单一元素的集合,如果引用缺失,返回一个空集合。 |
前置条件
Guava的Preconditions类提供前置条件判断方法,每个方法都有三个变种:
- 没有额外参数:抛出的异常中没有错误消息;
- 有一个Object对象作为额外参数:抛出的异常使用Object.toString() 作为错误消息;
- 有一个String对象作为额外参数,并且有一组任意数量的附加Object对象:这个变种处理异常消息的方式有点类似printf,但考虑GWT的兼容性和效率,只支持%s指示符。
private static void test1(int i, int j) {
// i > j是判断的表达式," "中是errorMessage
checkArgument(i > j, "except i > j , but exactly %s > %s", j , i);
}
public static void main(String[] args) {
test1(1, 3);
}
方法声明(不包括额外参数) | 描述 | 检查失败时抛出的异常 |
checkArgument(boolean) | 检查boolean是否为true,用来检查传递给方法的参数。 | IllegalArgumentException |
checkNotNull(T) | 检查value是否为null,该方法直接返回value,因此可以内嵌使用checkNotNull。 | NullPointerException |
checkState(boolean) | 用来检查对象的某些状态。 | IllegalStateException |
checkElementIndex(int index, int size) | 检查index作为索引值对某个列表、字符串或数组是否有效。index>=0 && index<size * | IndexOutOfBoundsException |
checkPositionIndex(int index, int size) | 检查index作为位置值对某个列表、字符串或数组是否有效。index>=0 && index<=size * | IndexOutOfBoundsException |
checkPositionIndexes(int start, int end, int size) | 检查[start, end]表示的位置范围对某个列表、字符串或数组是否有效 | IndexOutOfBoundsException |
Objects方法
Object.equals(o1, o2)可以避免对象为null出现异常的情况
public class HelloWord{
public static void main(String[] args) {
System.out.println("a".hashCode());
System.out.println(Objects.hashCode("a"));
System.out.println(Objects.hash("a"));
}
}
输出结果:97
97
128
"a".hashCode()和Objects.hashCode("a")都是直接调用Object对象的hashCode方法
Objects.hash()可以传入多个对象作为参数,调用的Arrays.hashCode()方法
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
比较器
// 所有比较结果均为true返回0, 比较链遇到false直接返回-1
int res = ComparisonChain.start().compare(o1, o2)......result();
private static void test2() {
System.out.println(ComparisonChain.start()
.compare(1, 1)
.compare("a", "a")
.result());
System.out.println(ComparisonChain.start().
compare("a", "b")
.compare(1, 1)
.result());
}
输出:0
-1
排序器「Ordering」
Guava库中实现Comparator的类,提供多种静态方法对对象进行排序
List<String> strings = Arrays.asList("apple", null, "banana", "cat", null);
Ordering<@Nullable Comparable> ordering = Ordering.natural().nullsLast();
strings.sort(ordering);
System.out.println(strings);
// 输出: [apple, banana, cat, null, null]
- natural():自然排序,根据对象的自然顺序进行排序,例如对于字符串,按照字典顺序进行排序;对于数字,按照数值大小进行排序。
- nullsLast():用于将空对象放在排序的最后。如果排序的对象中存在空对象,通过调用nullsLast()方法,空对象将被放在排序结果的最后。
当阅读链式调用产生的排序器时,应该从后往前读,因为每次链式调用都是用后面的方法包装了前面的排序器。在一个链中最多使用三个方法,以免造成理解困难
异常
void propagateIfInstanceOf( Throwable, Class<X extends Exception>) throws X | Throwable类型为X才抛出 |
void propagateIfPossible( Throwable) | Throwable类型为Error或RuntimeException才抛出 |
void propagateIfPossible( Throwable, Class<X extends Throwable>) throws X | Throwable类型为X, Error或RuntimeException才抛出 |
集合
不可变集合
不可变对象有很多优点,包括:
- 当对象被不可信的库调用时,不可变形式是安全的;
- 不可变对象被多个线程调用时,不存在线程安全问题;
- 不可变集合不需要考虑变化,因此可以节省时间和空间。所有不可变的集合都比它们的可变形式有更好的内存利用率(分析和测试细节);
- 不可变对象因为有固定不变,可以作为常量来安全使用。
重要提示:所有Guava不可变集合的实现都不接受null值。我们对Google内部的代码库做过详细研究,发现只有5%的情况需要在集合中允许null元素,剩下的95%场景都是遇到null值就快速失败。
如果你需要在不可变集合中使用null,请使用JDK中的Collections.unmodifiableXXX方法。
构造方式:
- 使用of方法:使用静态的of方法,可以快速创建不可变集合。
例如,使用ImmutableList.of()
可以创建一个空的不可变列表;使用ImmutableList.of("a", "b", "c")
可以创建一个包含元素的不可变列表。类似地,还有ImmutableSet.of()和ImmutableMap.of()等方法。 - 使用copyOf方法:使用copyOf方法可以将现有的集合复制为不可变集合。
例如,使用ImmutableList.copyOf(list)
可以将现有的列表复制为一个不可变列表。 - 使用Builder模式:对于复杂的集合构造,可以使用Builder模式来构建不可变集合。
例如,使用ImmutableList.builder().add("a").add("b").add("c").build()
可以创建一个包含元素的不可变列表。 - 使用Builder的from方法:使用Builder的from方法可以根据现有的集合创建不可变集合。
例如,使用ImmutableList.<String>builder().addAll(list).build()
可以根据现有的列表创建一个不可变列表。
所有不可变集合都有一个asList()
方法提供ImmutableList视图,来帮助你用列表形式方便地读取集合元素。例如,你可以使用sortedSet.asList().get(k)
从ImmutableSortedSet中读取第k个最小元素。
asList()返回的ImmutableList通常是——并不总是——开销稳定的视图实现,而不是简单地把元素拷贝进List。也就是说,asList返回的列表视图通常比一般的列表平均性能更好,比如,在底层集合支持的情况下,它总是使用高效的contains方法。
可变集合接口 | 属于JDK还是Guava | 不可变版本 |
Collection | JDK | ImmutableCollection |
List | JDK | ImmutableList |
Set | JDK | ImmutableSet |
SortedSet/NavigableSet | JDK | ImmutableSortedSet |
Map | JDK | ImmutableMap |
SortedMap | JDK | ImmutableSortedMap |
Multiset | Guava | ImmutableMultiset |
SortedMultiset | Guava | ImmutableSortedMultiset |
Multimap | Guava | ImmutableMultimap |
ListMultimap | Guava | ImmutableListMultimap |
SetMultimap | Guava | ImmutableSetMultimap |
BiMap | Guava | ImmutableBiMap |
Guava | ImmutableClassToInstanceMap | |
Table | Guava | ImmutableTable |
MultiSet
可以多次添加相等的元素,并且可以统计重复元素的个数
可以用两种方式看待Multiset:
- 没有元素顺序限制的ArrayList<E>
- Map<E, Integer>,键为元素,值为计数
// 1.使用HashMultiset(基于哈希表的实现)
Multiset<@Nullable Object> hashMultiset = HashMultiset.create();
// 2.使用TreeMultiset(基于红黑树的实现),可以对元素进行排序
TreeMultiset<Comparable> treeMultiset = TreeMultiset.create();
// 3.使用LinkedHashMultiset(基于链表的实现),保留元素的插入顺序
LinkedHashMultiset<@Nullable Object> linkedHashMultiset = LinkedHashMultiset.create();
// 4.使用EnumMultiset(基于枚举类型的实现),适用于枚举类型的元素
EnumMultiset<MyEnum> enumMultiset = EnumMultiset.create(MyEnum.class);
// 5.
ConcurrentHashMultiset<Object> concurrentHashMultiset = ConcurrentHashMultiset.create();
// 6.
ImmutableMultiset<Integer> immutableMultiset = ImmutableMultiset.<Integer>builder().setCount(1,4).add(3).build();
方法 | 描述 |
count(E) | 给定元素在Multiset中的计数 |
elementSet() | Multiset中不重复元素的集合,类型为Set<E> |
entrySet() | 和Map的entrySet类似,返回Set<Multiset.Entry<E>>,其中包含的Entry支持getElement()和getCount()方法 |
add(E, int(required = false)) | 增加给定元素在Multiset中的计数 |
addAll(Collection<? extends E> c) | 增加给定集合中的元素 |
remove(E, int(required = false)) | 减少给定元素在Multiset中的计数 |
removeAll(Collection<? extends E> c) | 移除与给定集合中的元素相同的所有元素,而不会考虑元素的数量 |
setCount(E, int) | 设置给定元素在Multiset中的计数,不可以为负数 |
size() | 返回集合元素的总个数(包括重复的元素) |
elementSet() | 返回集合中所有不重复的元素(可以对返回集合调用size()获得不重复元素个数) |
MultiMap
Multimap是把键映射到任意多个值的一般方式,类似Map<K, Collection<V>>
可以用两种方式思考Multimap的概念:”键-单个值映射”的集合:
a -> 1 a -> 2 a ->4 b -> 3 c -> 5
或者”键-值集合映射”的映射:
a -> [1, 2, 4] b -> 3 c -> 5
实现 | 键行为类似 | 值行为类似 |
HashMap | ArrayList | |
HashMap | HashSet | |
LinkedHashMap | LinkedList | |
LinkedHashMap | LinkedHashMap | |
TreeMap | TreeSet | |
ImmutableMap | ImmutableList | |
ImmutableMap | ImmutableSet |
除了两个不可变形式的实现,其他所有实现都支持null键和null值
LinkedListMultimap.entries()保留了所有键和值的迭代顺序。
LinkedHashMultimap保留了映射项的插入顺序,包括键插入的顺序,以及键映射的所有值的插入顺序。
BiMap
可以实现键值对的双向映射,键和值都是唯一的
HashBiMap<@Nullable Object, @Nullable Object> hashBiMap = HashBiMap.create();
ImmutableBiMap<Integer, Integer> immutableBiMap = ImmutableBiMap.<Integer, Integer>builder().put(1, 2).build();
EnumBiMap<MyEnum, MyEnum> enumBiMap = EnumBiMap.create(MyEnum.class, MyEnum.class);
EnumHashBiMap<MyEnum, @Nullable Object> enumHashBiMap = EnumHashBiMap.create(MyEnum.class);
Table
Table<R, C, V>类比于Map<K1, Map<K2, V>>
HashBasedTable<Object, Object, Object> hashBasedTable = HashBasedTable.create();
TreeBasedTable<Comparable, Comparable, Object> treeBasedTable = TreeBasedTable.create();
ImmutableTable<Object, Object, Object> immutableTable = ImmutableTable.builder().put(1, 1, 1).build();
ArrayTable.create(Iterable<? extends R> rowKeys, Iterable<? extends C> columnKeys);
ArrayTable要求在构造时就指定行和列的大小,本质上由一个二维数组实现,以提升访问速度和密集Table的内存利用率。
// 源码
private ArrayTable(Iterable<? extends R> rowKeys, Iterable<? extends C> columnKeys) {
this.rowList = ImmutableList.copyOf(rowKeys);
this.columnList = ImmutableList.copyOf(columnKeys);
Preconditions.checkArgument(this.rowList.isEmpty() == this.columnList.isEmpty());
this.rowKeyToIndex = Maps.indexMap(this.rowList);
this.columnKeyToIndex = Maps.indexMap(this.columnList);
V[][] tmpArray = new Object[this.rowList.size()][this.columnList.size()];
this.array = tmpArray;
this.eraseAll();
}
集合包装类
集合接口 | 属于JDK还是Guava | 对应的Guava工具类 |
Collection | JDK | :不要和java.util.Collections混淆 |
List | JDK | |
Set | JDK | |
SortedSet | JDK | |
Map | JDK | |
SortedMap | JDK | |
Queue | JDK | |
Guava | ||
Guava | ||
Guava | ||
Guava |
缓存
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(MY_LISTENER)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。
通常来说,Guava Cache适用于:
- 你愿意消耗一些内存空间来提升速度。
- 你预料到某些键会被查询一次以上。
- 缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached这类工具)
如果你的场景符合上述的每一条,Guava Cache就适合你。
如同范例代码展示的一样,Cache实例通过CacheBuilder生成器模式获取,但是自定义你的缓存才是最有趣的部分。
注:如果你不需要Cache中的特性,使用ConcurrentHashMap有更好的内存效率——但Cache的大多数特性都很难基于旧有的ConcurrentMap复制,甚至根本不可能做到。
Guava Cache | ConcurrentMap | |
功能 | Guava库中的一个功能强大的缓存实现,提供了各种缓存策略,如基于大小、时间和引用的缓存淘汰策略。它还支持缓存加载和异步刷新机制 | 是Java标准库中的接口,提供了一组线程安全的Map操作。允许多个线程同时访问和修改Map,而不需要额外的同步操作 |
数据存储 | 使用内存作为默认的数据存储,并提供了一些缓存大小限制的策略,如最大缓存项数、最大缓存大小等。 | 可以使用不同的实现,如ConcurrentHashMap,可以将数据存储在内存中,也可以使用其他持久化存储。 |
缓存策略 | 基于大小的缓存淘汰策略(如最大缓存项数)、基于时间的缓存淘汰策略(如过期时间)、基于引用的缓存淘汰策略(如弱引用和软引用)等 | 本身不提供缓存策略,但可以通过编程方式实现一些自定义的缓存策略 |
API | 提供了一组丰富的API,用于管理缓存,如获取缓存值、添加缓存项、手动移除缓存项、清除整个缓存等。它还提供了加载器(CacheLoader)和刷新器(CacheRefresher)等功能 | 提供了基本的Map操作,如添加元素、获取元素、删除元素等,但没有提供像Guava Cache那样的高级功能 |
加载
在使用缓存之前需要先考虑有没有合理的默认方法来加载或计算与键关联的值?
如果有的话,你应当使用CacheLoader。
如果没有,或者你想要覆盖默认的加载运算,同时保留"获取缓存-如果没有-则计算"[get-if-absent-compute]的原子语义,你应该在调用get时传入一个Callable实例。
缓存元素也可以通过Cache.put方法直接插入,但自动加载是首选的,因为它可以更容易地推断所有缓存内容的一致性。
Cache<Object, Object> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.build();
LoadingCache<Integer, String> myCache = CacheBuilder.newBuilder()
.maximumSize(100)
.build(new CacheLoader<Integer, String>() {
// 默认用来加载key对应value的方法
@Override
public String load(Integer key) throws Exception {
return String.valueOf(key);
}
});
当调用get(key)方法时,实际会调用localCache.getOrLoad(key)
方法,通过方法名可以看出包含get和load两个操作,首先会根据key在缓存中获取对应值,如果值不存在,则根据CacheLoader加载新值。在这个过程中,会遍历哈希桶中的缓存项,尝试找到与给定键匹配的缓存项,然后判断其值是否可用或过期。如果值可用且未过期,则返回值;否则,触发相应的移除通知并尝试加载新值。
如果你定义的CacheLoader没有声明任何检查型异常,则可以通过getUnchecked(key)查找缓存;但必须注意,一旦CacheLoader声明了检查型异常,就不可以调用getUnchecked(key)。getUnchecked(key)方法会将CacheLoader可能存在的ExecutionException包装为UncheckedExecutionException,这是一个运行时异常。所以,在使用getUnchecked(key)方法时,不需要在代码中捕获或声明处理这个异常。
private static void test1() throws ExecutionException {
myCache.get(1);
}
private static void test2() {
myCache.getUnchecked(1);
}
private static void test3() {
try {
myCache.get(1);
}catch (Exception e) {
// handle exception
}
}
getAll(Iterable<? extends K>)方法用来执行批量查询。默认情况下,对每个不在缓存中的键,getAll方法会单独调用CacheLoader.load来加载缓存项。如果批量的加载比多个单独加载更高效,你可以重载CacheLoader.loadAll来利用这一点。
注:CacheLoader.loadAll
的实现可以为没有明确请求的键加载缓存值。例如,为某组中的任意键计算值时,能够获取该组中的所有键值,loadAll方法就可以实现为在同一时间获取该组的其他键值。getAll(Iterable<? extends K>)
方法会调用loadAll,但会筛选结果,只会返回请求的键值对。
Callable
所有类型的Guava Cache,不管有没有自动加载功能,都支持get(K, Callable<V>)方法。这个方法返回缓存中相应的值,或者用给定的Callable运算并把结果加入到缓存中。在整个加载方法完成前,缓存项相关的可观察状态都不会更改。这个方法简便地实现了模式"如果有缓存则返回;否则运算、缓存、然后返回"。
myCache.get(key, new Callable<String>() {
@Override
public String call() throws Exception {
// 通过key获取到对应value的逻辑,如:
String value = String.valueOf(key);
// 添加对应value到缓存
myCache.put(key, value);
return value;
}
});
缓存回收
基于容量的回收(size-based eviction)
如果要规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)
。缓存将尝试回收最近没有使用或总体上很少使用的缓存项。
——警告:在缓存项的数目达到限定值之前,缓存就可能进行回收操作——通常来说,这种情况发生在缓存项的数目逼近限定值时。
另外,不同的缓存项有不同的“权重”(weights)——例如,如果你的缓存值,占据完全不同的内存空间,你可以使用CacheBuilder.weigher(Weigher)
指定一个权重函数,并且用CacheBuilder.maximumWeight(long)
指定最大总重。
在权重限定场景中,要注意
- 回收是在重量逼近限定值时就进行了
- 重量是在缓存创建时计算的,因此要考虑重量计算的复杂度
Cache<Integer, Integer> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.maximumWeight(10)
.weigher(new Weigher<Integer, Integer>() {
@Override
public int weigh(Integer key, Integer value) {
return value % 10;
}
}).build();
定时回收(Timed Eviction)
expireAfterAccess(long, TimeUnit)
:缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。expireAfterWrite(long, TimeUnit)
:缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。
Cache<Integer, Integer> cache = CacheBuilder.newBuilder()
.expireAfterAccess(100, TimeUnit.SECONDS)
.expireAfterWrite(100, TimeUnit.MINUTES)
.build();
基于引用的回收(Reference-based Eviction)
通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以把缓存设置为允许垃圾回收:
CacheBuilder.weakKeys()
:使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。CacheBuilder.weakValues()
:使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。CacheBuilder.softValues()
:使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。
对于基于引用的回收:因为垃圾回收仅依赖恒等式(==),使用弱引用键的缓存用==而不是equals比较键。
显式清除
- 个别清除:
Cache.invalidate(key)
- 批量清除:
Cache.invalidateAll(keys)
- 清除所有缓存项:
Cache.invalidateAll()
监听器
通过CacheBuilder.removalListener(RemovalListener)
,你可以声明一个监听器,以便缓存项被移除时做一些额外操作。
缓存项被移除时,RemovalListener会获取移除通知RemovalNotification,其中包含移除原因RemovalCause、键和值。
请注意,RemovalListener抛出的任何异常都会在记录到日志后被丢弃[swallowed]。
警告:默认情况下,监听器方法是在移除缓存时同步调用的。因为缓存的维护和请求响应通常是同时进行的,代价高昂的监听器方法在同步模式下会拖慢正常的缓存请求。在这种情况下,你可以使用RemovalListeners.asynchronous(RemovalListener, Executor)
把监听器装饰为异步操作。
// 创建了一个同步的 RemovalListener。当缓存中的条目被移除时,监听器中的onRemoval方法会同步执行
RemovalListener<String, String> listener = new RemovalListener<String, String>() {
@Override
public void onRemoval(RemovalNotification<String, String> notification) {
System.out.println("Key: " + notification.getKey() + ", Value: " + notification.getValue() + " was removed.");
}
};
// 将listener设置为异步执行
// 创建了一个单线程的 Executor 对象,用于执行异步操作,将这个 Executor 对象作为参数传递给 RemovalListeners.asynchronous() 方法
RemovalListeners.asynchronous(listener, Executors.newSingleThreadExecutor());
Cache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterAccess(100, TimeUnit.SECONDS)
.expireAfterWrite(100, TimeUnit.MINUTES)
.removalListener(listener)
.build();
清理与刷新
使用CacheBuilder构建的缓存不会"自动"执行清理和回收工作,也不会在某个缓存项过期后马上清理,也没有诸如此类的清理机制。
相反,它会在写操作时顺带做少量的维护工作,或者偶尔在读操作时做——如果写操作实在太少的话。
这样做的原因在于:如果要自动地持续清理缓存,就必须有一个线程,这个线程会和用户操作竞争共享锁。此外,某些环境下线程创建可能受限制,这样CacheBuilder就不可用了。
相反,我们把选择权交到你手里。如果你的缓存是高吞吐的,那就无需担心缓存的维护和清理等工作。
如果你的 缓存只会偶尔有写操作,而你又不想清理工作阻碍了读操作,那么可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp()
。ScheduledExecutorService可以帮助你很好地实现这样的定时调度。
LoadingCache.refresh(K)
刷新表示为键加载新值,这个过程可以是异步的。在刷新操作进行时,缓存仍然可以向其他线程返回旧值,而不像回收操作,读缓存的线程必须等待新值加载完成。
「如果在build时没有定义CacheLoader,创建的是Cache对象,没有refresh()方法,定义后返回的是LoadingCache对象,有refresh()方法」
Cache<Object, Object> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.build();
LoadingCache<Integer, String> myCache = CacheBuilder.newBuilder()
.maximumSize(100)
.build(new CacheLoader<Integer, String>() {
// 默认用来加载key对应value的方法
@Override
public String load(Integer key) throws Exception {
return String.valueOf(key);
}
@Override
public ListenableFuture<String> reload(Integer key, String oldValue) throws Exception {
return super.reload(key, oldValue);
}
});
如果刷新过程抛出异常,缓存将保留旧值,而异常会在记录到日志后被丢弃[swallowed]。
重载CacheLoader.reload(K, V)
可以扩展刷新时的行为,这个方法允许开发者在计算新值时使用旧的值。
CacheBuilder.refreshAfterWrite(long, TimeUnit)
可以为缓存增加自动定时刷新功能。
和expireAfterWrite相反,refreshAfterWrite通过定时刷新可以让缓存项保持可用
注意:缓存项只有在被检索时才会真正刷新(如果CacheLoader.refresh实现为异步,那么检索不会被刷新拖慢)。因此,如果你在缓存上同时声明expireAfterWrite和refreshAfterWrite,缓存并不会因为刷新盲目地定时重置,如果缓存项没有被检索,那刷新就不会真的发生,缓存项在过期时间后也变得可以回收。
资料参考: