Guava 缓存教程

xiaoxiao2021-02-28  12

Guava 缓存

本文我们看下Guava Cache的实现,包括基本使用,驱逐策略,刷新缓存以及一些有趣的批处理操作。最后,我们再看看缓存发出的删除通知功能。

如何使用Guava Cache

先来看一个简单示例,缓存字符串实例的大小形式。首先,我们创建ChcheLoader,用于计算存储在缓存中的值,然后我们便捷的CacheBuilder类依照规范构建缓存:

@Test public void whenCacheMiss_thenValueIsComputed() { CacheLoader<String, String> loader; loader = new CacheLoader<String, String>() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache<String, String> cache; cache = CacheBuilder.newBuilder().build(loader); assertEquals(0, cache.size()); assertEquals("HELLO", cache.getUnchecked("hello")); assertEquals(1, cache.size()); }

因为“hello” 键对应值在缓存中没有,所以值被计算并缓存。注意,我们使用getUnchecked() 方法,如果对应值不存在,则计算并缓存值到缓存中。

驱逐策略

每个缓存需要在必要时删除内容,让我们讨论下从缓存中清除值机制——使用不同的标准。

按大小驱逐

我们能通过 maximumSize()方法限制缓存大小,如果缓存达到上限,最老的项将被驱逐。 下面示例代码中,我们限制缓存大小为3条记录:

@Test public void whenCacheReachMaxSize_thenEviction() { CacheLoader<String, String> loader; loader = new CacheLoader<String, String>() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache<String, String> cache; cache = CacheBuilder.newBuilder().maximumSize(3).build(loader); cache.getUnchecked("first"); cache.getUnchecked("second"); cache.getUnchecked("third"); cache.getUnchecked("forth"); assertEquals(3, cache.size()); assertNull(cache.getIfPresent("first")); assertEquals("FORTH", cache.getIfPresent("forth")); }

按权重驱逐

我们也可以按照自定义权重功能限制缓存大小,下面代码,使用length作为我们自定义权重函数:

@Test public void whenCacheReachMaxWeight_thenEviction() { CacheLoader<String, String> loader; loader = new CacheLoader<String, String>() { @Override public String load(String key) { return key.toUpperCase(); } }; Weigher<String, String> weighByLength; weighByLength = new Weigher<String, String>() { @Override public int weigh(String key, String value) { return value.length(); } }; LoadingCache<String, String> cache; cache = CacheBuilder.newBuilder() .maximumWeight(16) .weigher(weighByLength) .build(loader); cache.getUnchecked("first"); cache.getUnchecked("second"); cache.getUnchecked("third"); cache.getUnchecked("last"); assertEquals(3, cache.size()); assertNull(cache.getIfPresent("first")); assertEquals("LAST", cache.getIfPresent("last")); }

注意,缓存可能删除多条记录,为较长记录预留空间。

按时间驱逐

除了按大小驱逐较早记录,我们还可以采用时间方式。下面示例我们自定义缓存删除已经闲置2ms的记录:

@Test public void whenEntryIdle_thenEviction() throws InterruptedException { CacheLoader<String, String> loader; loader = new CacheLoader<String, String>() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache<String, String> cache; cache = CacheBuilder.newBuilder() .expireAfterAccess(2,TimeUnit.MILLISECONDS) .build(loader); cache.getUnchecked("hello"); assertEquals(1, cache.size()); cache.getUnchecked("hello"); Thread.sleep(300); cache.getUnchecked("test"); assertEquals(1, cache.size()); assertNull(cache.getIfPresent("hello")); }

我们也可以基于整个生命时间来驱逐,下面示例中,被存储2ms后记录被清除缓存。

@Test public void whenEntryLiveTimeExpire_thenEviction() throws InterruptedException { CacheLoader<String, String> loader; loader = new CacheLoader<String, String>() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache<String, String> cache; cache = CacheBuilder.newBuilder() .expireAfterWrite(2,TimeUnit.MILLISECONDS) .build(loader); cache.getUnchecked("hello"); assertEquals(1, cache.size()); Thread.sleep(300); cache.getUnchecked("test"); assertEquals(1, cache.size()); assertNull(cache.getIfPresent("hello")); }

弱引用键(Weak key)

接下来,看看如何使缓存键为弱引用,即允许垃圾收集器收集在其他地方没有引用的缓存键。缺省缓存键和值都为强引用,但我们能使用weakKeys()方法使缓存键为弱引用,示例如下:

@Test public void whenWeakKeyHasNoRef_thenRemoveFromCache() { CacheLoader<String, String> loader; loader = new CacheLoader<String, String>() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache<String, String> cache; cache = CacheBuilder.newBuilder().weakKeys().build(loader); }

缓存值软引用(soft valuel)

我们可以使用softValues()方法让垃圾回收器收集缓存值,防止内存溢出:

@Test public void whenSoftValue_thenRemoveFromCache() { CacheLoader<String, String> loader; loader = new CacheLoader<String, String>() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache<String, String> cache; cache = CacheBuilder.newBuilder().softValues().build(loader); }

需要注意的时,软引用太多会影响系统性能,最好使用maximumSize()方法。

处理null值

现在,让我们看看缓存如何null值。默认情况下,Guava Cache如果加载null值会抛出异常,因为缓存null值没有意义。 但如果null值在你代码中有意义,那你可以使用Optional类实现:

@Test public void whenNullValue_thenOptional() { CacheLoader<String, Optional<String>> loader; loader = new CacheLoader<String, Optional<String>>() { @Override public Optional<String> load(String key) { return Optional.fromNullable(getSuffix(key)); } }; LoadingCache<String, Optional<String>> cache; cache = CacheBuilder.newBuilder().build(loader); assertEquals("txt", cache.getUnchecked("text.txt").get()); assertFalse(cache.getUnchecked("hello").isPresent()); } private String getSuffix(final String str) { int lastIndex = str.lastIndexOf('.'); if (lastIndex == -1) { return null; } return str.substring(lastIndex + 1); }

刷新缓存

下面看看如何刷新缓存值,可以使用refreshAfterWrite()方法自动刷新。下面示例中,每分钟自动刷新:

@Test public void whenLiveTimeEnd_thenRefresh() { CacheLoader<String, String> loader; loader = new CacheLoader<String, String>() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache<String, String> cache; cache = CacheBuilder.newBuilder() .refreshAfterWrite(1,TimeUnit.MINUTES) .build(loader); }

也可以手动通过refresh(key)方法刷新缓存中指定记录。

预缓存

我们可以通过putAll()方法在缓存中插入多条记录。下面示例中,通过Map往缓存中增加多条记录:

@Test public void whenPreloadCache_thenUsePutAll() { CacheLoader<String, String> loader; loader = new CacheLoader<String, String>() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache<String, String> cache; cache = CacheBuilder.newBuilder().build(loader); Map<String, String> map = new HashMap<String, String>(); map.put("first", "FIRST"); map.put("second", "SECOND"); cache.putAll(map); assertEquals(2, cache.size()); }

删除通知

有时当缓存记录删除时,需要处理一些业务,下面讨论下RemovalNotification。 我们可以注册RemovalListener 监听器获得删除通知,还可以通过getCause()方法访问删除原因。 下面示例中,缓存中第四个元素删除时会收到通知:

@Test public void whenEntryRemovedFromCache_thenNotify() { CacheLoader<String, String> loader; loader = new CacheLoader<String, String>() { @Override public String load(final String key) { return key.toUpperCase(); } }; RemovalListener<String, String> listener; listener = new RemovalListener<String, String>() { @Override public void onRemoval(RemovalNotification<String, String> n){ if (n.wasEvicted()) { String cause = n.getCause().name(); assertEquals(RemovalCause.SIZE.toString(),cause); } } }; LoadingCache<String, String> cache; cache = CacheBuilder.newBuilder() .maximumSize(3) .removalListener(listener) .build(loader); cache.getUnchecked("first"); cache.getUnchecked("second"); cache.getUnchecked("third"); cache.getUnchecked("last"); assertEquals(3, cache.size()); }

补充说明

最后,有一些关于Guava 缓存实现的补充说明:

是线程安全的

put(key,value)方法可以手动插入记录

CacheStats ( hitRate(), missRate(), ..)等方法可以监控缓存性能

总结

文本通过示例说明Guava缓存主要功能及应用,从简单使用到元素驱逐,刷新缓存,预缓存以及删除通知等。

转载请注明原文地址: https://www.6miu.com/read-2800170.html

最新回复(0)