Caffeine是Google基于Java8对GuavaCache的重写升级版本,支持丰富的缓存过期策略,尤其是 TinyLfu淘汰算法,提供了一个近乎最佳的命中率。从性能上《读、写、读/写)也足以秒杀其他一堆进程内缓存框架。Spring5更是直接放弃了使用了多年的Guava,而采用了Caffeine。
Caffeine的 API的操作功能和Guava是基本保持一致的,并且 Caffeine为了兼容之前是Guava的用户,做了一个Guava的 Adapter给大家使用也是十分的贴心。
Caffeine是一个非常不错的缓存框架,无论是在性能方面,还是在API方面,都要比Guava cache要优秀一些。如果在新的项目中要使用local cache的话,可以优先考虑使用Caffeine。对于老的项目,如果使用了Guava cache,想要升级为 Caffeine的话,可以使用Caffeine提供的 Guava cache适配器,方便的进行切换。
根据github官网的描述,对于jdk11 以上的jdk版本请使用3.1.x,否则使用2.9.x
For
Java 11 or above, use3.1.xotherwise use2.9.x.
首先先添加maven 依赖
<!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>
Cache<Key, Graph> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.build();
// 查找一个缓存元素, 没有查找到的时候返回null
Graph graph = cache.getIfPresent(key);
// 查找缓存,如果缓存不存在则生成缓存元素, 如果无法生成则返回null
graph = cache.get(key, k -> createExpensiveGraph(key));
// 添加或者更新一个缓存元素
cache.put(key, graph);
// 移除一个缓存元素
cache.invalidate(key);
- 这里是官方文档的描述
Cache接口提供了显式搜索查找、更新和移除缓存元素的能力。缓存元素可以通过调用
cache.put(key, value)方法被加入到缓存当中。如果缓存中指定的key已经存在对应>的缓存元素的话,那么先前的缓存的元素将会被直接覆盖掉。因此,通过cache.get(key, k -> value)的方>式将要缓存的元素通过原子计算的方式 插入到缓存中,以避免和其他写入进行竞争。值得注意的是,当缓存>的元素无法生成或者在生成的过程中抛出异常而导致生成元素失败,cache.get也许会返回null。
当然,也可以使用Cache.asMap()所暴露出来的ConcurrentMap的方法对缓存进行操作。
package cn.lazyfennec.caffeine;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
/**
* @Author: Neco
* @Description: 手动加载
* @Date: create in 2022/6/11 11:57
*/
public class ManualCaffeineDemo {
public void test() {
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // 设置过期时间
.maximumSize(10_000) // 设置缓存容量,其中下划线只是方便查看数字,没有实际意义,如这里就表示10000
.build();
// 根据key查找指定的缓存
String value = cache.getIfPresent("key");
System.out.println("[ first time value:" + value + " ]");
// 查询,如果没有获取到对应的数据,则执行查询获取方法,然后插入数据
value = cache.get("key", key->getKeyValue(key));
System.out.println("[ value of after get function return: " + value + " ]");
value = cache.getIfPresent("key");
System.out.println("[ value of after get function: " + value + " ]");
cache.put("key", "value of after put");
value = cache.getIfPresent("key");
System.out.println("[ value of after put: " + value + " ]");
}
// 获取数据的方法
private String getKeyValue(String key) {
System.out.println("====== try to get the key's value");
return key + "'s value";
}
public static void main(String[] args) {
ManualCaffeineDemo caffeineDemo = new ManualCaffeineDemo();
caffeineDemo.test();
}
}
[ first time value:null ]
====== try to get the key's value
[ value of after get function return: key's value ]
[ value of after get function: key's value ]
[ value of after put: value of after put ]
LoadingCache<Key, Graph> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
// Lookup and compute an entry if absent, or null if not computable
Graph graph = cache.get(key);
// Lookup and compute entries that are absent
Map<Key, Graph> graphs = cache.getAll(keys);
- 这里是官方文档的描述
一个LoadingCache是一个Cache 附加上 CacheLoader能力之后的缓存实现。
通过 getAll可以达到批量查找缓存的目的。 默认情况下,在getAll 方法中,将会对每个不存在对应缓存的key调用一次 CacheLoader.load 来生成缓存元素。 在批量检索比单个查找更有效率的场景下,你可以覆盖并开发CacheLoader.loadAll 方法来使你的缓存更有效率。
值得注意的是,你可以通过实现一个 CacheLoader.loadAll并在其中为没有在参数中请求的key也生成对应的缓存元素。打个比方,如果对应某个key生成的缓存元素与包含这个key的一组集合剩余的key所对应的元素一致,那么在loadAll中也可以同时加载剩下的key对应的元素到缓存当中。
package cn.lazyfennec.caffeine;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @Author: Neco
* @Description: 自动加载
* @Date: create in 2022/6/11 11:57
*/
public class LoadingCaffeineDemo {
public void test() {
LoadingCache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // 设置过期时间
.maximumSize(10_000) // 设置缓存容量,其中下划线只是方便查看数字,没有实际意义,如这里就表示10000
.build(key -> getKeyValue(key));
// 根据key查找指定的缓存
String value = cache.get("key");
System.out.println(value);
System.out.println("============================");
Map<String, String> all = cache.getAll(Arrays.asList("key1", "key2", "key3"));
System.out.println(all);
}
// 获取数据的方法
private String getKeyValue(String key) {
return key + "'s value";
}
public static void main(String[] args) {
LoadingCaffeineDemo caffeineDemo = new LoadingCaffeineDemo();
caffeineDemo.test();
}
}
key's value
============================
{key1=key1's value, key2=key2's value, key3=key3's value}
AsyncCache<Key, Graph> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.buildAsync();
// 查找一个缓存元素, 没有查找到的时候返回null
CompletableFuture<Graph> graph = cache.getIfPresent(key);
// 查找缓存元素,如果不存在,则异步生成
graph = cache.get(key, k -> createExpensiveGraph(key));
// 添加或者更新一个缓存元素
cache.put(key, graph);
// 移除一个缓存元素
cache.synchronous().invalidate(key);
- 这里是官方文档的描述
一个
AsyncCache是Cache的一个变体,AsyncCache提供了在 Executor上生成缓存元素并返回 CompletableFuture的能力。这给出了在当前流行的响应式编程模型中利用缓存的能力。
synchronous()方法给Cache提供了阻塞直到异步缓存生成完毕的能力。当然,也可以使用
AsyncCache.asMap()所暴露出来的ConcurrentMap的方法对缓存进行操作。默认的线程池实现是 ForkJoinPool.commonPool() ,当然你也可以通过覆盖并实现
Caffeine.executor(Executor)方法来自定义你的线程池选择。
package cn.lazyfennec.caffeine;
import com.github.benmanes.caffeine.cache.AsyncCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* @Author: Neco
* @Description: 手动异步加载
* @Date: create in 2022/6/11 11:57
*/
public class AsynchronousManualCaffeineDemo {
public void test() {
String key = "key";
AsyncCache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(3000, TimeUnit.MILLISECONDS)
.maximumSize(10_000)
.buildAsync();
// 查找一个缓存元素, 没有查找到的时候返回null
CompletableFuture<String> future = cache.getIfPresent(key);
System.out.println("first time, future: " + future);
System.out.println("=========================");
// 查找缓存元素,如果不存在,则异步生成
future = cache.get(key, k -> getKeyValue(key));
System.out.println("after try get, future = " + future);
future.thenAccept(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("accept ==== value: " + s);
}
});
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("future = " + future);
System.out.println("value = " + cache.synchronous().getIfPresent(key));
System.out.println("=====================================");
// 移除一个缓存元素
cache.synchronous().invalidate(key);
System.out.println("future = " + future);
System.out.println("value = " + cache.synchronous().getIfPresent(key));
System.out.println("=====================================");
// 超时自动销毁
CompletableFuture<String> future1 = cache.get("key1", this::getKeyValue);
try {
Thread.sleep(1002);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(cache.synchronous().getIfPresent("key1"));
// 继续等待超时
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(cache.synchronous().getIfPresent("key1"));
}
// 获取数据的方法
private String getKeyValue(String key) {
try {
Thread.sleep(1000);
return key + "'s value";
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
AsynchronousManualCaffeineDemo caffeineDemo = new AsynchronousManualCaffeineDemo();
caffeineDemo.test();
}
}
first time, future: null
=========================
after try get, future = java.util.concurrent.CompletableFuture@7ab2bfe1[Not completed, 1 dependents]
accept ==== value: key's value
future = java.util.concurrent.CompletableFuture@7ab2bfe1[Completed normally]
value = key's value
=====================================
future = java.util.concurrent.CompletableFuture@7ab2bfe1[Completed normally]
value = null
=====================================
key1's value
null
AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
// 你可以选择: 去异步的封装一段同步操作来生成缓存元素
.buildAsync(key -> createExpensiveGraph(key));
// 你也可以选择: 构建一个异步缓存元素操作并返回一个future
.buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
// 查找缓存元素,如果其不存在,将会异步进行生成
CompletableFuture<Graph> graph = cache.get(key);
// 批量查找缓存元素,如果其不存在,将会异步进行生成
CompletableFuture<Map<Key, Graph>> graphs = cache.getAll(keys);
- 这里是官方文档的描述
一个
AsyncLoadingCache是一个AsyncCache加上AsyncCacheLoader能力的实现。在需要同步的方式去生成缓存元素的时候,
CacheLoader是合适的选择。而在异步生成缓存的场景下,AsyncCacheLoader则是更合适的选择并且它会返回一个 CompletableFuture。通过
getAll可以达到批量查找缓存的目的。 默认情况下,在getAll方法中,将会对每个不存在对应缓存的key调用一次AsyncCacheLoader.asyncLoad来生成缓存元素。 在批量检索比单个查找更有效率的场景下,你可以覆盖并开发AsyncCacheLoader.asyncLoadAll方法来使你的缓存更有效率。值得注意的是,你可以通过实现一个
AsyncCacheLoader.asyncLoadAll并在其中为没有在参数中请求的key也生成对应的缓存元素。打个比方,如果对应某个key生成的缓存元素与包含这个key的一组集合剩余的key所对应的元素一致,那么在asyncLoadAll中也可以同时加载剩下的key对应的元素到缓存当中。
package cn.lazyfennec.caffeine;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* @Author: Neco
* @Description: 手动同步加载
* @Date: create in 2022/6/11 13:34
*/
public class AsynchronouslyLoadingCaffeineDemo {
public void test() throws Exception {
AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(2000, TimeUnit.MILLISECONDS)
.maximumSize(10_000)
.buildAsync(key -> getKeyValue(key));
String key1 = "key1";
//异步手动加载返回的不是值,而是 CompletableFuture
CompletableFuture<String> future = cache.get(key1);
//异步获取结果,对高性能场景有帮助
future.thenAccept(new Consumer<Object>() {
@Override
public void accept(Object o) {
//输出结果可以看到打印时间比生成时间晚
System.out.println(System.currentTimeMillis() + "->" + o);
}
});
Thread.sleep(4000);
//如果 cache 中 key 为空,直接返回 null,不为空则异步取值
CompletableFuture<String> ifPresent = cache.getIfPresent(key1);
if (ifPresent == null) {
System.out.println("null");
} else {
//异步取值
ifPresent.thenAccept(new Consumer<Object>() {
@Override
public void accept(Object o) {
System.out.println(o);
}
});
}
//批量异步取值,取不到则加载值
CompletableFuture<Map<String, String>> all = cache.getAll(Arrays.asList("key1", "key2", "key3"));
//批量异步获取
all.thenAccept(new Consumer<Map<String, String>>() {
@Override
public void accept(Map<String, String> objectObjectMap) {
System.out.println(objectObjectMap);
}
});
Thread.sleep(2001);
ConcurrentMap<String, String> asMap = cache.synchronous().asMap();
System.out.println(asMap);
}
// 获取数据的方法
private String getKeyValue(String key) {
return key + "_" + System.currentTimeMillis();
}
public static void main(String[] args) throws Exception {
AsynchronouslyLoadingCaffeineDemo caffeineDemo = new AsynchronouslyLoadingCaffeineDemo();
caffeineDemo.test();
}
}
1654926959737->key1_1654926959730
null
{key1=key1_1654926963748, key2=key2_1654926963749, key3=key3_1654926963749}
{}
// 基于缓存内的元素个数进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumSize(10_000)
.build(key -> createExpensiveGraph(key));
// 基于缓存内元素权重进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumWeight(10_000)
.weigher((Key key, Graph graph) -> graph.vertices().size())
.build(key -> createExpensiveGraph(key));
如果你的缓存容量不希望超过某个特定的大小,那么记得使用
Caffeine.maximumSize(long)。缓存将会尝试通过基于就近度和频率的算法来驱逐掉不会再被使用到的元素。另一种情况,你的缓存可能中的元素可能存在不同的“权重”--打个比方,你的缓存中的元素可能有不同的内存占用--你也许需要借助
Caffeine.weigher(Weigher)方法来界定每个元素的权重并通过Caffeine.maximumWeight(long)方法来界定缓存中元素的总权重来实现上述的场景。除了“最大容量”所需要的注意事项,在基于权重驱逐的策略下,一个缓存元素的权重计算是在其创建和更新时,此后其权重值都是静态存在的,在两个元素之间进行权重的比较的时候,并不会根据进行相对权重的比较。
package cn.lazyfennec.caffeine.eviction;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.RemovalListener;
/**
* @Author: Neco
* @Description: 基于大小-缓存大小
* @Date: create in 2022/6/11 14:51
*/
public class SizeBaseTest1 {
public static void main(String[] args) {
Cache<Object, Object> cache = Caffeine.newBuilder()
//缓存最大条数,超过这个条数就是驱逐缓存
.maximumSize(20)
.removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(Object k, Object v, RemovalCause removalCause) {
System.out.println("removed " + k + " cause " + removalCause.toString());
}
})
.build();
for (int i = 0; i < 25; i++) {
cache.put(i, i + "_value");
}
cache.cleanUp();
}
}
package cn.lazyfennec.caffeine.eviction;
import com.github.benmanes.caffeine.cache.*;
/**
* @Author: Neco
* @Description: 基于大小-权重
* @Date: create in 2022/6/11 14:53
*/
public class SizeBaseTest2 {
public static void main(String[] args) {
Cache<Object, Object> cache = Caffeine.newBuilder()
//缓存最大权重值
.maximumWeight(150)
//自定义计算权重
.weigher(new Weigher<Object, Object>() {
@Override
public int weigh(Object k, Object v) {
//这里为了简单,直接以 value 为权重
return (int) v;
}
})
.removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(Object k, Object v, RemovalCause removalCause) {
System.out.println("removed " + k + " cause " + removalCause.toString());
}
})
.build();
for (int i = 0; i < 20; i++) {
cache.put(i, 10);
}
cache.cleanUp();
}
}
// 基于固定的过期时间驱逐策略
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfterAccess(5, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
// 基于不同的过期驱逐策略
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfter(new Expiry<Key, Graph>() {
public long expireAfterCreate(Key key, Graph graph, long currentTime) {
// Use wall clock time, rather than nanotime, if from an external resource
long seconds = graph.creationDate().plusHours(5)
.minus(System.currentTimeMillis(), MILLIS)
.toEpochSecond();
return TimeUnit.SECONDS.toNanos(seconds);
}
public long expireAfterUpdate(Key key, Graph graph,
long currentTime, long currentDuration) {
return currentDuration;
}
public long expireAfterRead(Key key, Graph graph,
long currentTime, long currentDuration) {
return currentDuration;
}
})
.build(key -> createExpensiveGraph(key));
Caffeine提供了三种方法进行基于时间的驱逐:
- expireAfterAccess(long, TimeUnit): 一个元素在上一次读写操作后一段时间之后,在指定的时间后没有被再次访问将会被认定为过期项。在当被缓存的元素时被绑定在一个session上时,当session因为不活跃而使元素过期的情况下,这是理想的选择。
- expireAfterWrite(long, TimeUnit): 一个元素将会在其创建或者最近一次被更新之后的一段时间后被认定为过期项。在对被缓存的元素的时效性存在要求的场景下,这是理想的选择。
- expireAfter(Expiry): 一个元素将会在指定的时间后被认定为过期项。当被缓存的元素过期时间收到外部资源影响的时候,这是理想的选择。
在写操作,和偶尔的读操作中将会进行周期性的过期事件的执行。过期事件的调度和触发将会在O(1)的时间复杂度内完成。
为了使过期更有效率,可以通过在你的Cache构造器中通过Scheduler接口和Caffeine.scheduler(Scheduler) 方法去指定一个调度线程代替在缓存活动中去对过期事件进行调度。使用Java 9以上版本的用户可以选择Scheduler.systemScheduler()利用系统范围内的调度线程。
当测试基于时间的驱逐策略的时候,不需要坐在板凳上等待现实时钟的转动。使用Ticker接口和 Caffeine.ticker(Ticker)方法在你的Cache构造器中去指定一个时间源可以避免苦苦等待时钟转动的麻烦。Guava的测试库也提供了FakeTicker去达到同样的目的。
package cn.lazyfennec.caffeine.eviction;
import com.github.benmanes.caffeine.cache.*;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* @Author: Neco
* @Description: 基于时间
* @Date: create in 2022/6/11 14:59
*/
public class TimeBaseTest1 {
public static void main(String[] args) throws Exception {
LoadingCache<Object, Object> cache = Caffeine.newBuilder()
//基于时间失效->写入之后开始计时失效
.expireAfterWrite(2000, TimeUnit.MILLISECONDS)
//or 基于时间失效->访问之后开始计时失效
//.expireAfterAccess(10, TimeUnit.SECONDS)
//自定义线程池异步执行 remove 监听
.executor(Executors.newSingleThreadExecutor())
.removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(Object k, Object v, RemovalCause removalCause) {
System.out.println("缓存失效了 removed " + k + " cause " + removalCause.toString());
}
})
//同步加载和手动加载的区别就是在构建缓存时提供一个同步的加载方法
.build(new CacheLoader<Object, Object>() {
//单个 key 的值加载
@Override
public Object load(Object key) throws Exception {
System.out.println("---exec load---");
return key + "_" + System.currentTimeMillis();
}
});
//放入缓存
cache.put("k1", "v1");
//准备失效
Thread.sleep(2001);
System.out.println("sleep done");
System.out.println("我要开始取失效的缓存了");
Object v1 = cache.get("k1");
System.out.println("新值 " + v1);
System.exit(1);
}
}
sleep done
我要开始取失效的缓存了
---exec load---
缓存失效了 removed k1 cause EXPIRED
新值 k1_1654930796026
// 当key和缓存元素都不再存在其他强引用的时候驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.weakKeys()
.weakValues()
.build(key -> createExpensiveGraph(key));
// 当进行GC的时候进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.softValues()
.build(key -> createExpensiveGraph(key));
Caffeine 允许你配置你的缓存去让GC去帮助清理缓存当中的元素,其中key支持弱引用,而value则支持弱引用和软引用。记住 AsyncCache不支持软引用和弱引用。
Caffeine.weakKeys() 在保存key的时候将会进行弱引用。这允许在GC的过程中,当key没有被任何强引用指向的时候去将缓存元素回收。由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等(==)而不是对象相等 equals()去进行key之间的比较。
Caffeine.weakValues()在保存value的时候将会使用弱引用。这允许在GC的过程中,当value没有被任何强引用指向的时候去将缓存元素回收。由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等(==)而不是对象相等 equals()去进行value之间的比较。
Caffeine.softValues()在保存value的时候将会使用软引用。为了相应内存的需要,在GC过程中被软引用的对象将会被通过LRU算法回收。由于使用软引用可能会影响整体性能,我们还是建议通过使用基于缓存容量的驱逐策略代替软引用的使用。同样的,使用 softValues() 将会通过引用相等(==)而不是对象相等 equals()去进行value之间的比较。
package cn.lazyfennec.caffeine.eviction;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.RemovalListener;
/**
* @Author: Neco
* @Description: 基于引用;Java4种引用的级别由高到低依次为:强引用 > 软引用 > 弱引用 > 虚引用
* @Date: create in 2022/6/11 15:06
*/
public class ReferenceBaseTest1 {
public static void main(String[] args) throws Exception {
// Evict when neither the key nor value are strongly reachable
// 当key和value都没有引用时驱逐缓存
LoadingCache<String, Object> cache1 = Caffeine.newBuilder()
// 使用弱引用存储key
.weakKeys()
// 使用弱引用存储value
.weakValues()
// 开启统计功能
.recordStats()
.removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(Object k, Object v, RemovalCause removalCause) {
System.out.println("cache1 removed " + k + " cause " + removalCause.toString());
}
})
.build(key -> createTestValue(key));
// Evict when the garbage collector needs to free memory
// 当垃圾收集器需要释放内存时驱逐
LoadingCache<String, Object> cache2 = Caffeine.newBuilder()
// 使用软引用存储value
.softValues()
// 开启统计功能
.recordStats()
.removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(Object k, Object v, RemovalCause removalCause) {
System.out.println("cache2 removed " + k + " cause " + removalCause.toString());
}
})
.build(key -> createTestValue(key));
Object obj1 = new Object();
Object obj2 = new Object();
cache1.put("1234", obj1);
cache2.put("1234", obj2);
obj1 = new String("123");
obj2 = new String("123");
// 主动gc
System.gc();
System.out.println(cache1.getIfPresent("1234"));
System.out.println(cache2.getIfPresent("1234"));
/*
stats()方法会返回CacheS tats 对象以提供如下统计信息:
hitRate():缓存命中率;
averageLoadPenalty():加载新值的平均时间,单位为纳秒;
evictionCount():缓存项被回收的总数,不包括显式清除。
*/
System.out.println(cache1.stats());
System.out.println(cache2.stats());
}
private static Object createTestValue(String key) {
return key + "_" + System.currentTimeMillis();
}
}
null
java.lang.Object@4bf558aa
CacheStats{hitCount=0, missCount=1, loadSuccessCount=0, loadFailureCount=0, totalLoadTime=0, evictionCount=0, evictionWeight=0}
CacheStats{hitCount=1, missCount=0, loadSuccessCount=0, loadFailureCount=0, totalLoadTime=0, evictionCount=0, evictionWeight=0}

如果觉得有收获就点个赞吧,更多知识,请点击关注查看我的主页信息哦~
有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url
我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b
我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。
我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=
//1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json
在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear