基本工具
optional
null值的问题
map.get(key)== null ;并不知道是没有该值还是整个map是空
Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5
Optional.of(T):创建指定引用的Optioanl实例,引用为null快速失败
查询api
is present:判断若为非null引用返回true
T get():若引用缺失(null)返回异常
T or(T):若引用缺失返回指定的值
T orNull():若引用缺失返回null
Set<T> asSet:引用存在返回一个只有单一元素的集合,
引用缺失返回空集合
Guava的排序
创建排序器
提供的Ordering类基于Comparator的静态方法实现,提供链式调用定制和增强现有比较器
1.natural():对可排序类型做自然排序,如数字按大小,日期按先后
2.usingToString():按对象的字符串形式做字典排序
3.from(Comparator):把给定的Comparator转换为排序器
Ordering<String> byLengthOrdering = new Ordering<String>() {
public int compare(String left, String right) {
return Ints.compare(left.length(), right.length());
}
}
链式调用方法
1.reverse:获取语义相反的排序器
2.nullsFirst():使用当前排序器,把额外的null值排到最前面
3.nullsLast():使用当前排序器,把额外的null值排到最后面
4.compound(Comparator):合成新的比较器,处理当前排序器中相等的情况
5.lexicographical():基于处理类型T的排序器,返回该类型的可迭代对象Iterable<T>的排序器
6.onResultOf(Function):对集合元素中调用Function,再按返回值用当前排序器排序
代码例子:
class Foo {
@Nullable String sortedBy;
int notSortedBy;
}
Ordering<Foo> ordering = Ordering.natural().nullsFirst()
.onResultOf(new Function<Foo, String>() {
public String apply(Foo foo) {
return foo.sortedBy;
}
});
链式调用按照从后向前:
排序器先调用apply方法获取sortrdBy值,并把sortedBy为null的元素放到最前面,把剩下的元素按sortedBy进行自然排序,(每次链式调用都是用后面的方法包装了前面的排序器)
Guava同样提供可以操纵若干集合或元素值的方法
1.greatestOf(Iterable iterable,int k):获取可迭代对象中最大的k个元素
2.idOrdered(Iterable):判断迭代器中的对象是否已经按照规定排序
3.sortedCopy(Iterable):判断迭代器中的对象是否已经按照规定排序,不允许排序值相等的元素
4.min(E,E,E....)返回参数中最小的哪一个
5.min(Iterable)返回迭代器中最小的元素,如果没有数据抛异常
缓存
实例:缓存的设计方案,看java面试题
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 缓存适用于单个应用运行时的本地缓存,他不把数据存在文件或者外部服务器
加载
1.CacheLoader:以默认的方式加载key - value;
不支持缓存null,如果load调用返回null,则在get时候抛出异常(需要业务处理null)
2.如果没有默认的加载运算,但是要保留get - if absent- compute的原子语义,需要调用get的时候传入callable实例。
CacheLoader
loadingCache 是附带CacheLoader构建而成的缓存实现,创建自己的CacheLoader通常只需要简单的实现V load(key)
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
...
try {
return graphs.get(key);
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}
getAll(Iterable<?extends k>)
getAll 方法会单独调用CacheLoader.load来加载缓存,同样可以重载CacheLoader.loadAll来批量加载
或者使用
CacheBuilder.newBuilder().build(CacheLoader.from(this::getDeviceCodeFromDb));
这种方式,直接加载全量数据(从数据库中读取)
Callable
所有的Guava cache 不管有没有自动加载的动能,都是支持get(k,callable),callable使用运算并将结构置于缓存中
该方法实现了如果有缓存就返回,否则运算,缓存,然后返回
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(new CacheLoader<Key, Graph>() {
@Override
public Graph load(Key key) throws Exception {
return create(key);
}
});
private void test(Key key){
graphs.get(key, new Callable<Graph>() {
@Override
public Graph call() throws Exception {
return doSomeThing();
}
});
}
显示插入
数据插入缓存可以使用cache.put,或者使用cache.asmap视图提供的方法也能修改缓存,但是asMap修改缓存不能保证修改的缓存被原子的加载到缓存中
结论
相比于cache.asMap.putifAbsent(k,v);cache.get(k,callable)应该总是优先使用
缓存回收
基于容量回收
两个方面
1.基于数量回收:在构建缓存时候使用 maxinumSize(long),当缓存中的数目达到限定之前就可能回收
2.基于权重回收:在构建缓存时候使用 maxinumWeight(long),指定最大的权重数,使用cacheBuilder.weigher(Weigher)创建一个函数用于计算值得权重
基于定时回收
cache.expireAfterAccess(long,TimeUnit):缓存项再给定时间内没有被读写,则回收
cache.expireAfterWrite(long, TimeUnit)缓存项在给定的时间内没有被写访问,则回收
基于引用回收
通过使用weekKeys()弱引用的键,或者weekValues()弱引用的值,或者softValues()软引用的值,guava cache可以把缓存设置为允许垃圾回收
显示清除
个别键清除 cache.invalidate();
批量清除cache.invalidateAll(keys)
清除所有的缓存项目 cache.invalidateAll()
移除监听器
通过cacheBuilder.removalListener(RemovalListener)
,声明一个监听器
当缓存项被移除时候,RemoveListener会获取通知(RemovalNotifiaction),其中包含移除原因,键,值
默认情况下,监听器方法就是在移除缓存的时候同步的调用,但是同步调用可能影响用户对缓存的请求,所以我们可以将监听器修饰为异步操作
RemovalListener.asynchronous(RemovalLitener,Executor)
刷新
LoadingCache.refresh()刷新表示为键加载新值,这个过程可以是异步的,在刷新的过程中仍然可以返回旧的值,出现异常的时候也会保留旧的值
CacheBuilder.refreshAfterWrite(long,TimeUnit)
为缓存自动添加定时刷新的新功能,和expireAfterWrite相反,refresh可以保持缓存刷新时缓存的可用性
缓存项只有被检索的时候才会进行刷新,同时声明了expire和refresh,如果一直没有检索,如果到时间了,缓存项就会变得可以回收
支持异步刷新
当重写了reload方法会异步的reload,否则默认情况下是同步的
LoadingCache<String,String> loadingCache1=CacheBuilder.newBuilder()
.build(new CacheLoader<String, String>() {
@Override
public ListenableFuture<String> reload(String key, String oldValue) throws Exception {
return refreshPools.submit(new Callable<String>() {
//交给线程池异步处理
@Override
public String call() throws Exception {
return getData(key);
}
}
});
统计
CacheBuidler.recordStats()可以开启缓存的统计时间
hitRate():缓存命中率
avaerageLoadPenalty():加载新值得平均时间
evictionCount():缓存项被回收的总数,不包括显示的清除
asMap视图
asMap提供了缓存的currenthashmap形式,
cache.asMap()包含了所有加载到缓存的项
asMap.getKey,等同于cache.getIFPresent(key)。
所以的asMap的读写操作都会重置相关缓存项的访问时间,包含Cache.asMap.get()/put()
补充参数
1.maximumSize( long ):设置缓存Size 的上限,单位是键值对的个数。
2.concunrrentcyLevel(int ):定义当前缓存所支持的并发级别的上限,Guava提供了设置并发级别的api,使得缓存支持并发的写入和读取。同ConcurrentHashMap类似Guava cache的并发也是通过分离锁实现。在一般情况下,将并发级别设置为服务器cpu核心数是一个比较不错的选择。
3.get() vs getUnchecked()
get(key)会默认从缓存查,如果查询不到则电泳CacheLoader加载,但是cacheLoader会抛出异常,
get会抛出两种异常ExecutionException,或者未检测的异常UncheckedExecutionException
getUnchecked(k) 方法,这个方法会将所有的异常包装在 UncheckedExecutionException 异常中
问题点
refreshAfterWrite:当缓存项上一次更新操作后多久会被刷新。但是不会主动的移除key,只有再有新的请求进来以后才会执行reload,所以有种情况是:在吞吐量较低的情况下(后台在异步刷新,但是当前可以访问,此时访问就有可能是旧的值):解决办法使用:expire 和 refresh 混合使用
举例:
.refreshAfterWrite(20, TimeUnit.MINUTES)
.expireAfterWrite(30, TimeUnit.MINUTES)
第一次请求load数据,20分钟内都是读的缓存数据,20分钟以后数据应该刷新了,但是如果一直没有访问就不会进行刷新,然后30分钟之前(20分钟以后)都没有数据进行访问,此时就要进行对key的移除,下次读就是重新加载,但是如果一直有数据访问,那就一直异步刷新key不会进行数据的清理。