Guava Cache 提供了基本操作,eviction policies,refresh cache 這些功能
How to Use
@Test
public void whenCacheMiss_thenValueIsComputed() {
// caching the uppercase form of String instances
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);
// getUnchecked,會透過 CacheLoader 的 load 載入 cache 裡面,並取得 cache 的結果
assertEquals(0, cache.size());
assertEquals("HELLO", cache.getUnchecked("hello"));
assertEquals(1, cache.size());
assertEquals("HELLO", cache.getUnchecked("Hello"));
assertEquals(2, cache.size());
}
Eviction Policies
cache size
cache size with weight function
idle time
total live time
@Test
public void whenCacheReachMaxSize_thenEviction() {
// 設定 cache 的 maximumSize(3)
CacheLoader<String, String> loader = createLoader();
LoadingCache<String, String> cache;
cache = CacheBuilder.newBuilder().maximumSize(3).build(loader);
cache.getUnchecked("first");
cache.getUnchecked("second");
cache.getUnchecked("third");
// 放入第四個,會將第一個 cached 資料刪除
cache.getUnchecked("forth");
assertEquals(3, cache.size());
assertNull(cache.getIfPresent("first"));
assertEquals("FORTH", cache.getIfPresent("forth"));
}
@Test
public void whenCacheReachMaxWeight_thenEviction() {
CacheLoader<String, String> loader = createLoader();
// 定義 Weigher 的 weight function
// 以 cached object 的 value 的字串長度,作為 weight
Weigher<String, String> weighByLength;
weighByLength = new Weigher<String, String>() {
@Override
public int weigh(String key, String value) {
return value.length();
}
};
// maximumWeight 設定 cache 的 weight 總和限制
// weigher 指定 weight function class
LoadingCache<String, String> cache;
cache = CacheBuilder.newBuilder()
.maximumWeight(15)
.weigher(weighByLength)
.build(loader);
cache.getUnchecked("first");
cache.getUnchecked("second");
cache.getUnchecked("third");
// 要加入 third 時,會檢查 weight 總和,這邊總和會變成 16 就超過上限 15,因此 first 會被刪除
assertEquals(2, cache.size());
cache.getUnchecked("last");
assertEquals(3, cache.size());
assertNull(cache.getIfPresent("first"));
assertEquals("LAST", cache.getIfPresent("last"));
}
@Test
public void time_thenEviction()
throws InterruptedException {
CacheLoader<String, String> loader = createLoader();
// 當 cached object 已經 idle 2ms 後,就被移除
// expireAfterAccess
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"));
/////
// 當 cached object 儲存 2ms 後,就被移除
// expireAfterWrite
LoadingCache<String, String> cache2;
cache2 = CacheBuilder.newBuilder()
.expireAfterWrite(2,TimeUnit.MILLISECONDS)
.build(loader);
cache2.getUnchecked("hello");
assertEquals(1, cache2.size());
Thread.sleep(300);
cache2.getUnchecked("test");
assertEquals(1, cache2.size());
assertNull(cache2.getIfPresent("hello"));
}
CacheLoader<String, String> createLoader() {
CacheLoader<String, String> loader;
loader = new CacheLoader<String, String>() {
@Override
public String load(String key) {
return key.toUpperCase();
}
};
return loader;
}
Weak Keys
@Test
public void whenWeakKeyHasNoRef_thenRemoveFromCache() {
CacheLoader<String, String> loader;
loader = new CacheLoader<String, String>() {
@Override
public String load(String key) {
return key.toUpperCase();
}
};
// weakKeys 定義 cache 的 key 是用 weakReference
// garbage collector 可在 key 不被 referenced 的時候,將 key 回收
LoadingCache<String, String> cache;
cache = CacheBuilder.newBuilder().weakKeys().build(loader);
}
Soft Values
@Test
public void whenSoftValue_thenRemoveFromCache() {
CacheLoader<String, String> loader;
loader = new CacheLoader<String, String>() {
@Override
public String load(String key) {
return key.toUpperCase();
}
};
// softValues
// 可讓 garbage collector 回收 cached values
// 但如果使用太多 soft references 會影響效能,故還是建議使用 maxmimumSize
LoadingCache<String, String> cache;
cache = CacheBuilder.newBuilder().softValues().build(loader);
}
null Values
@Test
public void whenNullValue_thenOptional() {
// 如果要 laod null value,預設會 throw exception
// 但如果一定要使用 null value,可利用 Optional
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);
}
refresh Cache
更新 cache value 的方法,分為 manual/auto 兩種
@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> cache0;
cache0 = CacheBuilder.newBuilder().build(loader);
cache0.getUnchecked("hello");
try {
// get 會取得舊的 cachec value
String value = cache0.get("hello");
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
// 呼叫 refresh,(非同步) 更新 cached value
cache0.refresh("hello");
//////////////
// CacheBuilder.refreshAfterWrite(duration) 可自動 refresh cached value
// 如果 1min 後沒有 get 這個 key,當 cached value 有舊的值,其他 threads 會回傳舊值
// 如果 cached value 沒有值,就更新 value,其他 threads 會等待
LoadingCache<String, String> cache;
cache = CacheBuilder.newBuilder()
.refreshAfterWrite( 1,TimeUnit.MINUTES)
.build(loader);
}
Preload the Cache
@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 的 putAll,一次加入多個 cache values
Map<String, String> map = new HashMap<String, String>();
map.put("first", "FIRST");
map.put("second", "SECOND");
cache.putAll(map);
assertEquals(2, cache.size());
}
RemovalNotification
@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 可在 cachec value 被移除時,收到通知
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());
}
Others
Cache 是 thread-safe
可透過 put(key,value) ,不透過 CacheLoader 直接寫入 cache
cache.put("key", "value"); assertEquals("value", cache.getUnchecked("key"));
可用 CacheStats ( hitRate(), missRate(), ..) 量測 performance
沒有留言:
張貼留言