在 Spring Boot 应用程序中,要本地缓存一些数据,就读/写操作而言,哪种技术更好? HashMap vs ConcurrentHashMap vs LoadingCache(Guava library) 我尝试了对每个选项进行的写入和读取操作,其中 HashMap 最快,而 LoadingCache 最慢,那么我们为什么要使用 LoadingCache?它有什么特殊用途吗?
编辑: 该应用程序是多线程的。 并且可以牺牲缓存的最大大小、过期时间等功能。 此外,主要目的是提高读取速度。
在 Spring Boot 应用程序中,要本地缓存一些数据,就读/写操作而言,哪种技术更好? HashMap vs ConcurrentHashMap vs LoadingCache(Guava library) 我尝试了对每个选项进行的写入和读取操作,其中 HashMap 最快,而 LoadingCache 最慢,那么我们为什么要使用 LoadingCache?它有什么特殊用途吗?
编辑: 该应用程序是多线程的。 并且可以牺牲缓存的最大大小、过期时间等功能。 此外,主要目的是提高读取速度。
关于性能问题,它取决于你的数据大小和读取之间修改的比率。以下是一个建议:
静态数据:如果你的数据是静态的,在构造函数中初始化一个只读映射,代码如下:
final Map map;
MyClass(Map inputMap) {
map = Map.copyOf(inputMap);
}
Object get(Object key) {
return map.get(key);
}
罕见的修改: 如果您有罕见的修改并且数据不太大:
volatile Map map = Map.of();
Object synchronized put(Object key, Object value) {
Map mutable = new HashMap(map);
mutable.put(key, value);
map = Map.copyOf(mutable);
}
Object get(Object key) {
return map.get(key);
}
Map.copyOf
从Java 9开始提供。它创建了一个不可变的哈希表,该哈希表使用开放寻址方案,不同于HashMap
。这将比HashMap
更快。您还可以在多线程环境中使用上述方案的HashMap
,因为它在创建后不会被修改。synchronized
可以确保在多个线程同时使用put
时不使用更新。需要使用volatile
以确保更新在其他线程中可见。
因此,上述解决方案将提供最佳的读取速度,但牺牲了更新速度。主要目的是增加读取速度。
大量数据和/或大量修改:请使用ConcurrentHashMap
。
ConcurrentHashMap
,原因如下:
ConcurrentHashMap
是有效的。您会编写具有多个线程验证代码正确工作的单元测试吗?脚注:
使用缓存
缓存和LoadingCache
:Guava LoadingCache
用于与CacheLoader
一起使用。缓存加载器有助于使缓存自动填充缓存和/或进行刷新。同时,Guava缓存已过时,我建议查看Caffine或cache2k,以获取在Java堆中工作的缓存解决方案。
缓存始终具有额外的开销,因为它需要执行一些簿记以了解当前访问哪些条目。在cache2k中,此开销最小,至少根据我的(免责声明…)基准测试。
Spring Boot
当与Spring缓存抽象一起使用时,例如使用@Cacheable
,则实现之间不会有很大的性能差异,因为缓存抽象也具有非常重要的开销。
Spring中的简单cache
实现仅适用于测试和原型设计。我建议尽快使用真正的缓存实现并设置合理的资源限制。
分析和优化整个应用程序
您所做的每个优化都具有权衡,因此您应该始终查看整个应用程序并将“优化”与最简单或最常见的解决方案进行比较。
HashMap
不是一个选项。它不是线程安全的,如果您使用锁等进行保护,则锁定很可能成为并发瓶颈。ConcurrentHashMap
没有提供很好的方法来实现。如果您希望您的缓存对内存压力敏感(例如使用弱引用键),则ConcurrentHashMap
也无法实现。Cache
类的用处所在。阅读this以了解它们提供的功能。
HashMap
最具性能,但它可能无法提供您所需的所有功能。性能并非一切。HashMap.get
或ConcurrentHashMap.get
会增加几个数量级。Guava 也是如此。 - Stephen C