Java本地缓存

本文介绍了Java内存缓存的概念,解释了为何使用缓存以提升性能和减轻数据库压力。重点讨论了Guava Cache,它是Google Guava库中的一个内存缓存解决方案,适合在单机环境中存储常用数据,以实现快速访问。Guava Cache适用于预期会被多次查询的键,并且内存占用在可接受范围内的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java内存缓存

什么是缓存

  1. 在计算中,缓存是一个高速数据存储层,其中存储了数据子集,且通常是短暂性存储,这样日后再次请求此数据时,速度要比访问数据的主存位置快。通过缓存,您可以高效地重用之前检索或计算的数据。

为什么要用缓存

  1. 提升应用程序的性能。
  2. 降低数据库成本。
  3. 减少后端负载。
  4. 可预测的性能。
  5. 消除数据库热点。
  6. 提高读取吞吐量(IOPS)。

Java内存缓存

  1. 在Java应用中,对于访问频次高,更新少的数据,通常的方案是将这类数据加入缓存中。相对于从数据库中读取来说,读缓存效率会有很大提升。

  2. 在集群环境下,常用的分布式缓存有Redis,Memcached等。但在某些业务场景上,可能不需要去搭建一套复杂的分布式缓存系统,在单机环境下,通常是会希望使用内部的缓存。

  3. 自己实现一个小缓存。

    public class MapCacheDemo {
    
        // 我使用了  ConcurrentHashMap,线程安全的要求。
        //我使用SoftReference <Object>  作为映射值,因为软引用可以保证在抛出OutOfMemory之前,如果缺少内存,将删除引用的对象。
        //在构造函数中,我创建了一个守护程序线程,每5秒扫描一次并清理过期的对象。
        private static final int CLEAN_UP_PERIOD_IN_SEC = 5;
    
        private final ConcurrentHashMap<String, SoftReference<CacheObject>> cache = new ConcurrentHashMap<>();
    
        public MapCacheDemo() {
            Thread cleanerThread = new Thread(() -> {
                while (!Thread.currentThread().isInterrupted()) {
                    try {
                        Thread.sleep(CLEAN_UP_PERIOD_IN_SEC * 1000);
                        cache.entrySet().removeIf(entry -> Optional.ofNullable(entry.getValue()).map(SoftReference::get).map(CacheObject::isExpired).orElse(false));
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            });
            cleanerThread.setDaemon(true);
            cleanerThread.start();
        }
    
        public void add(String key, Object value, long periodInMillis) {
            if (key == null) {
                return;
            }
            if (value == null) {
                cache.remove(key);
            } else {
                long expiryTime = System.currentTimeMillis() + periodInMillis;
                cache.put(key, new SoftReference<>(new CacheObject(value, expiryTime)));
            }
        }
    
        public void remove(String key) {
            cache.remove(key);
        }
    
        public Object get(String key) {
            return Optional.ofNullable(cache.get(key)).map(SoftReference::get).filter(cacheObject -> !cacheObject.isExpired()).map(CacheObject::getValue).orElse(null);
        }
    
        public void clear() {
            cache.clear();
        }
    
        public long size() {
            return cache.entrySet().stream().filter(entry -> Optional.ofNullable(entry.getValue()).map(SoftReference::get).map(cacheObject -> !cacheObject.isExpired()).orElse(false)).count();
        }
    
        // 缓存对象value
        private static class CacheObject {
            private Object value;
            private long expiryTime;
    
            private CacheObject(Object value, long expiryTime) {
                this.value = value;
                this.expiryTime = expiryTime;
            }
    
            boolean isExpired() {
                return System.currentTimeMillis() > expiryTime;
            }
    
            public Object getValue() {
                return value;
            }
    
            public void setValue(Object value) {
                this.value = value;
            }
        }
    }
    

Guava Cache

  1. Guava Cache是google guaava中的一个内存缓存模块,用于将数据缓存到JVM内存中。实际项目开发中经常将一些常用的数据缓存起来方便快速访问。适用于:

    • 愿意消耗一些内存空间来提升效率。
    • 预料到某些键会被查询一次以上。
    • 缓存中存放的数据总量不会超出内存容量。
  2. Guava Cahce是单个应用运行时的本地缓存。它不把数据存放到文件或者外部服务器。如果这符合你的需求,用Memcached或Redis。

  3. 使用示例

    public class GuavaCacheDemo {
        public static void main(String[] args) throws ExecutionException {
            //缓存接口这里是LoadingCache,LoadingCache在缓存项不存在时可以自动加载缓存
            LoadingCache<String, User> userCache
                    //CacheBuilder的构造函数是私有的,只能通过其静态方法newBuilder()来获得CacheBuilder的实例
                    = CacheBuilder.newBuilder()
                    //设置并发级别为8,并发级别是指可以同时写缓存的线程数
                    .concurrencyLevel(8)
                    //设置写缓存后8秒钟过期
                    .expireAfterWrite(8, TimeUnit.SECONDS)
                    //设置写缓存后1秒钟刷新
                    .refreshAfterWrite(1, TimeUnit.SECONDS)
                    //设置缓存容器的初始容量为10
                    .initialCapacity(10)
                    //设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
                    .maximumSize(100)
                    //设置要统计缓存的命中率
                    .recordStats()
                    //设置缓存的移除通知
                    .removalListener(new RemovalListener<Object, Object>() {
                        @Override
                        public void onRemoval(RemovalNotification<Object, Object> notification) {
                            System.out.println(notification.getKey() + " 被移除了,原因: " + notification.getCause());
                        }
                    })
                    //build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
                    .build(
                            new CacheLoader<String, User>() {
                                @Override
                                public User load(String key) throws Exception {
                                    System.out.println("缓存没有时,从数据库加载" + key);
                                    // TODO jdbc的代码~~忽略掉
                                    return new User("tony" + key, key);
                                }
                            }
                    );
    
            // 第一次读取
            for (int i = 0; i < 20; i++) {
                User user = userCache.get("uid" + i);
                System.out.println(user);
            }
    
            // 第二次读取
            for (int i = 0; i < 20; i++) {
                User user = userCache.get("uid" + i);
                System.out.println(user);
            }
            System.out.println("cache stats:");
            //最后打印缓存的命中率等 情况
            System.out.println(userCache.stats().toString());
        }
    }
    
### Java本地缓存实现方式及常用库 在Java开发中,为了提高应用性能并减少对外部资源的依赖,通常会采用本地缓存技术来存储临时数据。以下是几种常见的Java本地缓存实现方式及其特点: #### 1. **HashMap 和 ConcurrentHashMap** 这是最基础也是最常见的本地缓存实现方式之一。`HashMap` 是一种简单的键值对容器,而 `ConcurrentHashMap` 则是为了满足高并发环境下的安全性和效率设计的集合类。 - 特点: - 支持快速查找和插入操作。 - 不具备任何内置的淘汰策略或过期机制。 - 需要开发者自行管理缓存生命周期以及清理逻辑。 适用于小型项目或者不需要复杂控制的小规模缓存场景[^1]。 ```java // 使用ConcurrentHashMap作为本地缓存的例子 import java.util.concurrent.ConcurrentHashMap; public class SimpleCache { private static final ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>(); public void put(String key, String value){ cache.put(key,value); } public String get(String key){ return cache.get(key); } } ``` --- #### 2. **Caffeine** `Caffeine` 是一个高性能的本地缓存库,其设计理念借鉴自 Guava Cache 并进行了优化改进,在实际生产环境中得到了广泛应用。 - 主要特性: - 提供多种缓存驱逐策略(如 LRU、LFU 等)。 - 可设置最大容量限制与超时失效时间。 - 自动处理多线程同步问题。 - 更高效的内存利用率。 代码示例展示了基本创建过程以及一些核心方法的应用[^2]: ```java import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import java.util.concurrent.TimeUnit; public class CaffeineExample { public static void main(String[] args) throws InterruptedException { // 构建带有TTL配置的缓存实例 Cache<String, String> cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.SECONDS) .maximumSize(100) .build(); // 插入一条记录到缓存里 cache.put("key", "value"); System.out.println(cache.getIfPresent("key")); // 输出"value" Thread.sleep(11_000); System.out.println(cache.getIfPresent("key")); // 此处可能已经因过期而返回null } } ``` --- #### 3. **Guava Cache** 由 Google 开发维护的工具集的一部分——`Guava Cache` 同样能够很好地胜任大多数日常任务所需的轻量级缓存需求。 - 功能亮点: - 定义明确的最大条目数约束条件。 - 时间维度上的自动清除能力(基于最后访问时刻计算)。 - 方便扩展定制行为的能力。 尽管如此,相较于最新版本的 Caffeine ,它的某些方面表现稍逊一筹[^1]^。 ```java import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class GuavaCacheDemo { LoadingCache<String,Integer> counterCache= CacheBuilder.newBuilder() .refreshAfterWrite(5,TimeUnit.MINUTES)//刷新周期设定为五分钟 .expireAfterAccess(10,TimeUnit.HOURS)//十小时无活动即销毁 .initialCapacity(10).maximumSize(1000) .recordStats()//开启统计数据收集模式 .build(new CacheLoader<>() { @Override public Integer load(String s)throws Exception{ return fetchCountFromDB(s); } }); int getCount(String id){try{return countersCache.get(id);}catch (ExecutionException e){throw new RuntimeException(e)};} private static int fetchCountFromDB(String itemId){return Random.nextInt();} } ``` --- #### 4. **Ehcache** 除了单纯的单机版外,`Ehcache` 还支持分布式的部署形式,因此非常适合那些既希望利用节点内部高速读取优势又担心单一故障点风险的企业级架构体系。 - 关键属性包括但不限于: - 数据分层结构(堆内/堆外RAM+磁盘文件持久化)。 - 异构平台间互操作性强。 - 易于与其他主流框架集成使用。 不过需要注意的是第三版以后有所调整方向更倾向于成为 JSR-107 的参考实现代理角色. --- #### 总结建议 针对不同的业务诉求可以选择合适的方案组合起来解决问题。如果只是单纯追求极致的速度体验的话那么像 ConcurrentMap 或者简单封装后的 Map 就足够应付大部分场合了;而对于更加复杂的场景则推荐尝试引入专门化的第三方组件比如前面提到过的几个知名产品。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值