Java实现本地缓存的四种方法实现与对比

 更新时间:2025年08月15日 08:49:47   作者:橘子编程  
本地缓存的优点就是速度非常快,没有网络消耗,本地缓存比如 caffine,guava cache 这些都是比较常用的,下面我们来看看这四种缓存的具体实现吧

本地缓存比如 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 {
            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()                         // 开启缓存统计功能,可以获取命中率等信息
                .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.printStackTrace(); // 捕获并打印异常信息
        }
    }
 
    // 模拟从数据库中根据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.newCacheManagerBuilder().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); // 打印获取到的缓存值
 
        // 5. 使用完缓存后,关闭缓存管理器以释放资源
        cacheManager.close();
    }
}

到此这篇关于Java实现本地缓存的四种方法实现与对比的文章就介绍到这了,更多相关Java本地缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mybatis-generator生成多次重复代码问题以及解决

    mybatis-generator生成多次重复代码问题以及解决

    在使用MySQL数据库时,如果多个数据库中存在相同表名,即使在URL中配置了数据库名,也可能导致数据互相影响,解决这一问题的方法是在mapper-generator-config.xml文件中添加catalog属性,明确指定逆向工程代码所涉及表的数据库名
    2024-10-10
  • Spring @Scheduled注解及工作原理

    Spring @Scheduled注解及工作原理

    Spring的@Scheduled注解用于标记定时任务,无需额外库,需配置@EnableScheduling,设置fixedRate、fixedDelay或cron,基于TaskScheduler,默认使用线程池,可自定义线程数及名称以优化性能,本文给大家介绍Spring @Scheduled注解的相关知识,感兴趣的朋友一起看看吧
    2025-06-06
  • Java线程之join_动力节点Java学院整理

    Java线程之join_动力节点Java学院整理

    join() 定义在Thread.java中,下文通过源码分享join(),需要的朋友参考下吧
    2017-05-05
  • Java的Hibernate框架中集合类数据结构的映射编写教程

    Java的Hibernate框架中集合类数据结构的映射编写教程

    Hibernate可以将Java中几个内置的集合结构映射为数据库使用的关系模型,下面我们就来看一下Java的Hibernate框架中集合类数据结构的映射编写教程:
    2016-07-07
  • Java数据结构优先队列实练

    Java数据结构优先队列实练

    通常都把队列比喻成排队买东西,大家都很守秩序,先排队的人就先买东西。但是优先队列有所不同,它不遵循先进先出的规则,而是根据队列中元素的优先权,优先权最大的先被取出,这篇文章主要介绍了java优先队列的真题,感兴趣的朋友一起看看吧
    2022-07-07
  • 关于synchronized有趣的同步问题

    关于synchronized有趣的同步问题

    今天小编就为大家分享一篇关于关于synchronized有趣的同步问题,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • Mybatis 动态sql的编写与开启二级缓存

    Mybatis 动态sql的编写与开启二级缓存

    二级缓存是Mapper级别的缓存,多个SqlSession去操作同一个Mapper中的SQL语句,则这些SqlSession可以共享二级缓存,即二级缓存是跨SqlSession的,这篇文章主要介绍了Mybatis 动态sql的编写|开启二级缓存,需要的朋友可以参考下
    2023-02-02
  • Java中防止数据重复提交超简单的6种方法

    Java中防止数据重复提交超简单的6种方法

    在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,这篇文章主要给大家介绍了关于Java中防止数据重复提交超简单的6种方法,需要的朋友可以参考下
    2021-11-11
  • Java线程组与未处理异常实例分析

    Java线程组与未处理异常实例分析

    这篇文章主要介绍了Java线程组与未处理异常,结合实例形式分析了java线程组处理异常的相关技巧与操作注意事项,需要的朋友可以参考下
    2019-09-09
  • Java实现的数字签名算法RSA完整示例

    Java实现的数字签名算法RSA完整示例

    这篇文章主要介绍了Java实现的数字签名算法RSA,结合完整实例形式详细分析了RSA算法的相关概念、原理、实现方法及操作技巧,需要的朋友可以参考下
    2019-09-09

最新评论