Java实现本地缓存的四种方法实现与对比
目录
- 1、HashMap
- 2、Guava Cache
- 3、Caffeine
- 4、Encache
本地缓存比如 caffine,guava cache 这些都是比较常用的,本地缓存的优点就是速度非常快,没有网络消耗,缺点就是应用重启后,缓存就会丢失。
Java缓存技术可分为远端缓存和本地缓存,远端缓存常用的方案有著名的Redis,而本地缓存的代表技术主要有HashMap,Guava Cache,Caffeine和Encahche。
1、HashMap
通过Map的底层方式,直接将需要缓存的对象放在内存中。
- 优点:简单粗暴,不需要引入第三方包,比较适合一些比较简单的场景。
- 缺点:没有缓存淘汰策略,定制化开发成本高。
package com.taiyuan.javademoone.cachedemo; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadwriteLock; /** * 定义一个基于LinkedHashMap实现的线程安全的LRU缓存类 */ public class LRUCache extends LinkedHashMap<Object, Object> { // 明确泛型类型为<Object, Object>,提高代码可读性 /** * 可重入读写锁,用于保证多线程环境下对缓存的并发读写操作的安全性 */ private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private Lock readLock = readWriteLock.readLock(); // 读锁,用于并发读取时共享访问 private Lock writeLock = readWriteLock.writeLock(); // 写锁,用于写入或修改时独占访问 /** * 缓存的最大容量限制,超过此容量将移除最久未使用的条目 */ private int maxSize; /** * 构造函数,初始化LRU缓存并设置最大容量 * * @param maxSize 缓存允许存储的最大条目数 */ public LRUCache(int maxSize) { // 调用父类LinkedHashMap的构造方法: // 参数1:初始容量为maxSize + 1,避免频繁扩容 // 参数2:负载因子为1.0f,表示哈希表填满到100%时才扩容 // 参数3:AccessOrder为true,表示按照访问顺序排序,实现LRU策略 super(maxSize + 1, 1.0f, true); this.maxSize = maxSize; } /** * 重写get方法,获取指定key对应的value,使用读锁保证线程安全 * * @param key 要查找的键 * @return 对应的值,如果不存在则返回null */ @Override public Object get(Object key) { readLock.lock(); // 加读锁,允许多个线程同时读 try { return super.get(key); // 调用父类的get方法 } finally { readLock.unlock(); // 确保读锁最终被释放 } } /** * 重写put方法,向缓存中添加或更新键值对,使用写锁保证线程安全 * * @param key 要插入或更新的键 * @param value 要插入或更新的值 * @return 之前与key关联的值,如果没有则返回null */ @Override public Object put(Object key, Object value) { writeLock.lock(); // 加写锁,确保同一时间只有一个线程可以写 try { php return super.put(key, value); // 调用父类的put方法 } finally { writeLock.unlock(); // 确保写锁最终被释放 } } /** * 重写removeEldestEntry方法,当缓存大小超过maxSize时,移除最久未使用的条目 * * @param eldest 最久未访问的键值对Entry * @return 如果当前缓存大小超过最大容量,则返回true,触发删除最老的条目;否则返回false */ @Override protected boolean removeEldestEntry(Map.Entry eldest) { return this.size() > maxSize; // 判断当前缓存大小是否超出限制 } // 测试主方法 public static void main(String[] args) { LRUCache cache = new LRUCache(3); cache.put("1", "one"); cache.put("2", "two"); cache.put("3", "three"); System.out.println(cache); // 输出:{1=one, 2=two, 3=three} } }
2、Guava Cache
Guava Cache 是 Google Guava 库中的一个本地缓存实现,它提供了以下主要特性:
- 自动加载:当缓存未命中时自动从指定来源加载数据
- 多种淘汰策略:支持基于大小、时间和引用的淘汰
- 统计功能:内置缓存命中率统计
- 线程安全:内置并发控制机制
- 监听器:支持缓存移除通知
<!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>33.4.8-jre</version> </dependency>
package com.helloworld.demo; import com.google.common.cache.*; import java.util.concurrent.TimeUnit; public class GuavaCacheExample { public static void main(String[] args) { // 创建一个Guava的LoadingCache实例,支持自动加载缓存项 LoadingCache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(100) // 设置缓存最大容量为100个条目 .expireAfterWrite(10, TimeUnit.MINUTES) // 设置缓存项在写入10分钟后自动过期 .recordStats() // 开www.devze.com启缓存统计功能,可以获取命中率等信息 .build(new CacheLoader<String, String>() { // 定义缓存未命中时的加载逻辑 @Override public String load(String key) throws Exception { // 当根据key获取不到缓存值时,调用此方法从数据源(如数据库)加载数据 return fetchDataFromDatabase(key); } }); try { // 第一次获取key为"user:1001"的值,由于缓存中没有,会触发load方法从数据库加载 System.out.println("第一次获取(从数据库加载): " + cache.get("user:1001")); // 第二次获取相同的key,此时缓存中已有该值,直接从缓存返回,不会再次加载 System.out.println("第二次获取(从缓存获取): " + cache.get("user:1001")); // 手动向缓存中放入一个键值对,绕过自动加载逻辑 cache.put("user:1002", "Manual Data"); // 获取手动放入的缓存值 System.out.println("手动放入的数据: " + cache.get("user:1002")); // 打印缓存的统计信息,如命中率、加载次数等 System.out.println("\n缓存统计:"); System.out.println(cache.stats()); // 手动移除指定key的缓存项 cache.invalidate("user:1001"); // 尝试获取已被移除的缓存项,返回null表示不存在 System.out.println("\n移除后获取: " + cache.getIfPresent("user:1001")); } catch (Exception e) { e.p编程客栈rintStackTrace(); // 捕获并打印异常信息 } } // 模拟从数据库中根据key获取数据的逻辑 private static String fetchDataFromDatabase(String key) { // 打印当前正在加载的key,用于观察加载行为 System.out.println("正在从数据库加载数据: " + key); try { Thread.sleep(500); // 模拟数据库查询的延迟,增加真实感 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 } // 返回模拟的数据库查询结果 return "Data for " + key; } }
3、Caffeine
Caffeine采用了W-TinyLFU(LUR和LFU的优点结合)开源的缓存技术。缓存性能接近理论最优,属于是Guava Cache的增强版。
Caffeine 是一个高性能的 Java 缓存库,它改进了 Guava Cache 的设计,具有以下特点:
- 优化的淘汰算法:采用 W-TinyLFU 算法,结合了 LRU 和 LFU 的优点
- 卓越的性能:读写性能接近理论最优值
- 异步支持:提供异步加载和刷新机制
- 丰富的特性:支持多种淘汰策略、权重计算、统计等功能
- 内存友好:相比 Guava Cache 减少约 50% 的内存占用
<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.9.3</version> </dependency>
package com.helloworld.demo; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import java.util.concurrent.TimeUnit; public class CaffeineCacheTest { public static void main(String[] args) throws Exception { // 创建一个 Caffeine 缓存实例(注意:原注释写的是 Guava Cache,实际使用的是 Caffeine) Cache<String, String> loadingCache = Caffeine.newBuilder() .initialCapacity(5) // 设置初始缓存容量为 5 个条目 .maximumSize(10) // 设置缓存最大容量为 10 个条目,超过时将按照策略淘汰 .expireAfterWrite(17, TimeUnit.SECONDS) // 写入后 17 秒过期 .expireAfterAccess(17, TimeUnit.SECONDS) // 最后一次访问后 17 秒过期 .build(); // 构建缓存实例 String key = "key"; // 定义缓存的键 loadingCache.put(key, "这是测试方法"); // 手动将键值对放入缓存 // 从缓存中获取指定键的值 String value = loadingCache.getIfPresent(key); System.out.println(" 从缓存中获取指定键(key)的值:" + value); // 输出:这是测试方法 // 将指定的键从缓存中移除(使其失效) loadingCache.invalidate(key); value = loadingCache.getIfPresent(key); System.out.println(" 从缓存中获取指定键(key)的值:" + value); // 输出:null } }
4、Encache
Ehcache是一个纯java的进程内缓存框架,具有快速、精干的特点。是hibernate默认的cacheprovider。
- 优点:支持多种缓存淘汰算法,包括LFU,LRU和FIFO;缓存支持堆内缓存,堆外缓存和磁盘缓存;支持多种集群方案,解决数据共享问题。
- 缺点:性能比Caffeine差
<dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> <version>3.10.8</version> </dependency>
package com.helloworld.demo; import org.ehcache.Cache; import org.ehcache.CacheManager; import org.ehcache.config.builders.CacheConfigurationBuilder; import org.ehcache.config.builders.CacheManagerBuilder; import org.ehcache.config.builders.ResourcePoolsBuilder; import org.ehcache.config.units.MemoryUnit; /** * Ehcache 基础使用示例类 */ public class EhcacheBasicExample { public static void main(String[] args) { // 1. 创建缓存管理器(CacheManager),它是管理所有缓存的核心对象 CacheManager cacheManager = CacheManagerBuilder.newCacheManagjavascripterBuilder().build(); // 初始化缓存管理器,使其可以开始工作 cacheManager.init(); // 2. 创建一个缓存配置,定义缓存的键值类型和存储策略 CacheConfigurationBuilder<String, String> config = CacheConfigurationBuilder .newCacheConfigurationBuilder( String.class, // 缓存键的类型为 String String.class, // 缓存值的类型为 String ResourcePoolsBuilder.heap(100) // 配置堆内内存缓存最多存储 100 个条目 ); // 3. 根据配置创建一个名为 "myCache" 的缓存实例 Cache<String, String> myCache = cacheManager.createCache("myCache", config); // 4. 使用缓存:存储和读取数据 myCache.put("key1", "value1"); // 往缓存中放入一个键值对 String value = myCache.get("key1"); // 从缓存中根据 key 获取对应的 value System.out.println("获取的值: " + value); // 打印获取到的缓存值 http://www.devze.com // 5. 使用完缓存后,关闭缓存管理器以释放资源 cacheManager.close(); } }
到此这篇关于Java实现本地缓存的四种方法实现与对比的文章就介绍到这了,更多相关Java本地缓存内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论