cache组件中核心的类和接口列举如下:
接口:

  • Cache 本地缓存的顶级接口,提供一些对缓存进行get,put的方法,以及获取缓存统计数据的方法等。
  • LoadingCache 继承了Cache接口,并另外提供了一些当get数据不存在时自动去load相关key(s)所对应的value(s)的契约(即接口中的抽象方法),具体实现见LoadingCache的具体实现类。
  • RemovalListener 监听器接口,在缓存被移除的时候用来做一些操作,与下面的RemovalNotification、RemovalCause配套使用。很明显这是个观察者模式的应用。
  • Weigher 权重的接口,提供int weigh(K key, V value)抽象方法,给缓存中的Entry赋予权重信息。

抽象类:

  • AbstractCache
    本身是抽象类,实现自Cache接口,基本没做什么实际的工作,大多数方法的实现只是简单抛出UnsupportedOperationException.该抽象类提供了Cache接口的骨架,为了避免子类直接继承Cache接口时必须实现所有抽象方法,这种手法在其他地方也很常见,个人觉得都算得上是一种设计模式了。
  • AbstractLoadingCache 继承自AbstractCache并实现了LoadingCache接口,目的也是提供一个骨架,其中的某些方法提供了在get不到数据时会自动Load数据的契约。
  • CacheLoader 抽象类,最核心的方法就是load,封装load数据的操作,具体如何laod与该抽象类的具体子类有关,只需要重写laod方法,就可以在get不到数据时自动去load数据到缓存中。
  • ForwardingCache
    装饰器模式的用法,所有对缓存的操作都委托给其他的实现了Cache接口的类,该抽象类中有一个抽象方法protected abstract
    Cache<K, V>
    delegate();不难推测出来,其他的方法中均使用了该代理。即类似get(key){delegate().get(key)}
  • ForwardingLoadingCache 自行推断,不解释。

类:

  • CacheBuilder 建造者模式的应用,通过该类来组装Cache,最后调用build方法来生成Cache的实例
  • CacheBuilderSpec 用来构建CacheBuilder的实例,其中提供了一些配置参数,用这些配置的参数来通过CacheBuilder实例最终构建Cache实例。
  • CacheStats 缓存使用情况统计信息,比如命中多少次,缺失多少次等等。
  • LocalCache 本地缓存最核心的类,Cache接口实例的代理人,Cache接口提供的一些方法内部均采委托给LocalCache实例来实现,LocalCache的具体实现类似于ConcurrentHashMap,也采用了分段的方式。
  • RemovalListeners 该类的文档中说的是A collection of common removal
    listeners.感觉这个类并不是这个作用,这个类提供了一个asynchronous(RemovalListener,
    Executor)的方法,代码如下:

     public static <K, V> RemovalListener<K, V> asynchronous(
    final RemovalListener<K, V> listener, final Executor executor) {
    checkNotNull(listener);
    checkNotNull(executor);
    return new RemovalListener<K, V>() {
    @Override
    public void onRemoval(final RemovalNotification<K, V> notification) {
    executor.execute(
    new Runnable() {
    @Override
    public void run() {
    listener.onRemoval(notification);
    }
    });
    }
    };
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    看这意思是将监听器转成异步执行的,也就是在移除缓存中的数据时,用异步的方法执行onRemoval操作,在onRemoval比较耗时的时候会提升性能,不至于阻塞对缓存的其他操作。

  • RemovalNotification 封装了RemovalCause,典型的观察者模式。

枚举类:

  • RemovalCause 引起移除原因的枚举类,如显示调用invalidate方法产生的移除,或者是调用replace方法时产生的移除,或者是垃圾回收导致的移除,或者是缓存过期导致的移除,或者是超过了缓存大小限制导致的移除等等。

下面列出Cache组件的特点:
- 自动加载Entry(key-value对)到缓存中
- 当缓存超过设定的最大大小时采用LRU算法进行缓存的剔除
- 可设定缓存过期时间,基于最后一次访问或者最后一次写入缓存两种方式
- keys自动使用WeakReference进行包裹
- values自动使用WeakReference或者SoftReference进行包裹
- 当Entry从缓存中剔除时会有通知机制
- 能够对缓存的使用情况进行统计。

使用示例:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(MY_LISTENER)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

首先调用newBuilder静态方法产生CacheBuilder对象,然后开始装配。maximumSize(10000)表示缓存最多存放10000个键值对,超过这个数会利用LRU算法进行剔除,expireAfterWrite(10, TimeUnit.MINUTES)表示在key-value对写入缓存之后10分钟过期(过期数据不会立刻进行清除,而是在下一次进行get操作的时候判断是否过期,若过期则剔除)。removalListener(MY_LISTENER)添加监听器,在进行剔除操作的时候会调用该监听器的onRemoval方法进行操作。MY_LISTENER代表实现了RemovalListener接口的子类对象。build()方法可以传入一个CacheLoader类的子类对象,该对象用来在get数据失败时进行数据的load操作,load操作的过程就在重写的load方法中。

注:如果不需要自动装载数据的功能,可以在最后的build()方法中不穿递任何参数。带不带CacheLoad类型参数的build方法代码如下所示:

  public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
checkWeightWithWeigher();
checkNonLoadingCache();
return new LocalCache.LocalManualCache<K1, V1>(this);
} public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build(
CacheLoader<? super K1, V1> loader) {
checkWeightWithWeigher();
return new LocalCache.LocalLoadingCache<K1, V1>(this, loader);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

可以看出来它们返回的Cache实例类型分别是LocalCache.LocalManualCache和LocalCache.LocalLoadingCache这两个静态类。再去LocalCache类里面稍微看一下这两个静态内部类的继承层次。

static class LocalManualCache<K, V> implements Cache<K, V>, Serializable

static class LocalLoadingCache<K, V> extends LocalManualCache<K, V>
implements LoadingCache<K, V>
  • 1
  • 2
  • 3
  • 4
  • 5

可以看到LocalLoadingCache在继承自LocalManualCache的基础上还实现了LoadingCache接口。也就是说该类的一些方法中涉及到自动加载数据到缓存中的功能。因此构建哪种Cache完全取决于自己的需求。

最后需要提出的是,还可以重写CacheLoader中的loadAll(Iterable keys)方法,该方法可以用来批量加载数据,在哪种场景下需要这个方法呢?举一个例子,比如根据key获取value的操作需要经过网络连接,比较耗时,则批量导入数据则可以大大节省时间,也就是发一次网络请求获取一批数据回来。如果重写了loadAll,需要利用批量加载数据,那么就需要相应地调用Cache实例的getAll(Iterable keys)方法进行数据的批量获取,批量获取的过程中,若有一批key对应的value没有在缓存中,则会调用该loadAll方法进行批量加载。若没有重写loadAll方法,则会依次调用load方法去进行加载,因此是否需要重写loadAll方法可以看是否批量加载能大大节省时间。
使用示例如下:

LoadingCache<Key, Graph> loadingCache= CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(MY_LISTENER)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
public Graph loadAll(Iterable<Key> keys) {
return createExpensiveGraphs(keys);
}
});} //用法:
Map<Key, Graph> mygraphs = loadingCache.getAll(keys);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

若需要的数据不在缓存中或者已过期,如果重写了loadAll方法,则getAll方法内部会去调用loadAll方法加载需要的数据到缓存中,如果没有重写loadAll方法,getAll内部会依次调用load方法进行数据的加载。见如下代码:

try {
Map<K, V> newEntries = loadAll(keysToLoad, defaultLoader);//该laodAll方法会在defaultLoader.loadAll()方法没有进行重写时抛出异常,被下面的catch捕获。(因为默认的loadAll重写的逻辑就是是简单地抛出一个异常。)
for (K key : keysToLoad) {
V value = newEntries.get(key);
if (value == null) {
throw new InvalidCacheLoadException("loadAll failed to return a value for " + key);
}
result.put(key, value);
}
} catch (UnsupportedLoadingOperationException e) {
// loadAll not implemented, fallback to load
for (K key : keysToLoad) {
misses--; // get will count this miss
result.put(key, get(key, defaultLoader));//捕获到异常说明没有重写loadAll方法,则在get方法中会依次调用defaultLoader的load方法进行载入
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

未完待续。。。。
如有不当之处还望指正!

Guava Cache 本地缓存组件浅析的更多相关文章

  1. Guava Cache本地缓存

    Guava介绍 Guava是一种基于开源的Java库,其中包含谷歌正在由他们很多项目使用的很多核心库. 这个库是为了方便编码,并减少编码错误. 这个库提供用于集合,缓存,支持原语,并发性,常见注解,字 ...

  2. Caffeine Cache-高性能Java本地缓存组件

    前面刚说到Guava Cache,他的优点是封装了get,put操作:提供线程安全的缓存操作:提供过期策略:提供回收策略:缓存监控.当缓存的数据超过最大值时,使用LRU算法替换.这一篇我们将要谈到一个 ...

  3. Guava Cache,Java本地内存缓存使用实践

    Guava Cache,网上介绍很多,我就不赘述了. 分享一篇好的文章: Guava Cache内存缓存使用实践-定时异步刷新及简单抽象封装 Google Guava 3-缓存 在原作者基础上,我做了 ...

  4. 本地缓存之GUAVA

    项目开发中,很多配置数据需要缓存,一般来说,开发人员都会手动写HashMap,HashSet或者ConcurrentHashMap,ConcurrentHashSet缓存数据,但是这样的缓存往往存在内 ...

  5. Guava Cache源码浅析

    1. 简介 Guava Cache是指在JVM的内存中缓存数据,相比较于传统的数据库或redis存储,访问内存中的数据会更加高效,无网络开销. 根据Guava官网介绍,下面的这几种情况可以考虑使用Gu ...

  6. springboot之本地缓存(guava与caffeine)

    1. 场景描述 因项目要使用本地缓存,具体为啥不用redis等,就不讨论,记录下过程,希望能帮到需要的朋友. 2.解决方案 2.1 使用google的guava作为本地缓存 初步的想法是使用googl ...

  7. 本地缓存google.guava及分布式缓存redis 随笔

    近期项目用到了缓存,我选用的是主流的google.guava作本地缓存,redis作分布式 缓存,先说说我对本地缓存和分布式缓存的理解吧,可能不太成熟的地方,大家指出,一起 学习.本地缓存的特点是速度 ...

  8. Guava的两种本地缓存策略

    Guava的两种缓存策略 缓存在很多场景下都需要使用,如果电商网站的商品类别的查询,订单查询,用户基本信息的查询等等,针对这种读多写少的业务,都可以考虑使用到缓存.在一般的缓存系统中,除了分布式缓存, ...

  9. 同时使用Redis缓存和Google Guava本地缓存注意事项(深拷贝和浅拷贝)

    目录 1.问题场景及说明 2.Redis 缓存是深拷贝 3.Guava本地缓存直接获取则是浅拷贝 4.如何实现Guava获取本地缓存是深拷贝? 1.问题场景及说明 系统中同时使用 Redis 缓存和 ...

随机推荐

  1. ubuntu解压时中文出现乱码

    一.乱码类似这样的:╫╩┴╧╖┤╤▌▓т╒╛╦┘╢╚│ 今天遇到需要上传十几G的图片,在wins上压缩成zip格式,在上传到服务器上,结果出现乱码.然后各种百度心塞. 最初查到原因: 这个主要是因为z ...

  2. UI自动化项目搭建(selenium+testng+java+maven)

    一 工具准备 selenium chormDriver 二 环境准备 三搭建步骤

  3. threejs立方体贴图产生边缘锯齿问题

    threejs立方体贴图产生边缘锯齿问题 立方体贴图边缘锯齿 解决后 经过试验测试发现, textureGrass.wrapS和 textureGrass.wrapT属性导致的. 解决方法1: 删掉t ...

  4. Java作业六(2017-10-30)

    /*游戏引擎包,播放音乐*/ import com.rupeng.game.GameCore; public class Mc implements Runnable{ public static v ...

  5. [Swift]LeetCode513. 找树左下角的值 | Find Bottom Left Tree Value

    Given a binary tree, find the leftmost value in the last row of the tree. Example 1: Input: 2 / \ 1 ...

  6. [Java]LeetCode690. 员工的重要性 | Employee Importance

    You are given a data structure of employee information, which includes the employee's unique id, his ...

  7. [Swift]LeetCode804. 唯一摩尔斯密码词 | Unique Morse Code Words

    International Morse Code defines a standard encoding where each letter is mapped to a series of dots ...

  8. springboot 实战之一站式开发体验

    都说springboot是新形势的主流框架工具,然而我的工作中并没有真正用到springboot: 都说springboot里面并没有什么新技术,不过是组合了现有的组件而已,但是自己却说不出来: 都说 ...

  9. server.properties 文件详解

    [转载]:server.properties 文件详解 # 每一个Broker在集群中的唯标识.即使Broker的IP地址发生了变化,broker.id只要没变,则不会影响consumers的消息情况 ...

  10. [Abp 源码分析]六、工作单元的实现

    0.简介 在 Abp 框架内部实现了工作单元,在这里讲解一下,什么是工作单元? Unit Of Work(工作单元)模式用来维护一个由已经被业务事物修改(增加.删除或更新)的业务对象组成的列表.Uni ...