Guava自加载缓存LoadingCache使用指南

news2025/2/6 5:52:56

第1章:引言

大家好,我是小黑,今天我们来聊聊缓存。在Java世界里,高效的缓存机制对于提升应用性能、降低数据库负担至关重要。想象一下,如果每次数据请求都要跑到数据库里取,那服务器岂不是要累趴了?这时候,缓存就显得尤为重要了。

那么,怎么实现一个既高效又好用的缓存呢?别急,咱们今天的主角——Guava的LoadingCache就是这样一个神器。LoadingCache,顾名思义,就是能够自动加载缓存的工具。它不仅能自动载入数据,还能按需刷新,简直是懒人救星!接下来,小黑就带大家一起深入探究Guava的这个强大功能。

第2章:Guava简介

Guava是Google开源的一款Java库,提供了一堆好用的工具类,从集合操作、缓存机制到函数式编程,应有尽有。使用Guava,咱们可以写出更简洁、更高效、更优雅的Java代码。今天,小黑重点要聊的是Guava中的缓存部分。

首先,让我们来看看Guava缓存的一个基本概念:LoadingCache。LoadingCache是Guava中一个提供自动加载功能的缓存接口。它允许咱们通过一个CacheLoader来指定如何加载缓存。这就意味着,当咱们尝试从缓存中读取一个值,如果这个值不存在,LoadingCache就会自动调用预定义的加载机制去获取数据,然后将其加入到缓存中,非常智能。

来,小黑先给大家展示一个简单的LoadingCache创建示例:

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

public class LoadingCacheExample {
    public static void main(String[] args) {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(100) // 最大缓存项数
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        return "Hello, " + key; // 定义缓存加载的方式
                    }
                });

        System.out.println(cache.getUnchecked("Guava")); // 输出:Hello, Guava
    }
}

在这个例子里,小黑创建了一个简单的LoadingCache实例。当咱们尝试通过getUnchecked方法获取一个缓存项时,如果这个项不存在,CacheLoader会自动加载一个新值。在这里,它就是简单地返回一个字符串。

第3章:LoadingCache基础

什么是LoadingCache呢?简单来说,它是Guava提供的一个缓存接口,能够自动加载缓存。当你尝试从缓存中读取一个值时,如果这个值不存在,LoadingCache会自动调用预定义的加载逻辑来获取这个值,然后存储到缓存中。这个过程完全自动化,省去了很多手动管理缓存的麻烦。

那么,LoadingCache的核心特性是什么呢?首先,它提供了自动的缓存加载机制,这意味着咱们不需要自己去写代码判断缓存是否存在或者过期。其次,它支持多种缓存过期策略,比如基于时间的过期、大小限制等,确保缓存的有效性。再者,LoadingCache还提供了缓存统计和监听的功能,方便咱们监控和调优缓存的使用。

来,让小黑用一个例子来展示一下LoadingCache的基本用法:

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import java.util.concurrent.ExecutionException;

public class LoadingCacheDemo {
    public static void main(String[] args) throws ExecutionException {
        // 创建一个CacheLoader
        CacheLoader<String, String> loader = new CacheLoader<String, String>() {
            @Override
            public String load(String key) {
                return key.toUpperCase(); // 模拟加载数据的过程
            }
        };

        // 使用CacheBuilder构建一个LoadingCache
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(100) // 设置最大缓存数为100
                .build(loader);

        // 使用缓存
        System.out.println(cache.get("hello")); // 输出: HELLO
        System.out.println(cache.get("guava")); // 输出: GUAVA
    }
}

在这个例子中,小黑创建了一个CacheLoader来定义加载数据的逻辑,这里就是简单地将字符串转换为大写。然后,使用CacheBuilder来构建一个LoadingCache实例,设置了最大缓存数为100。当调用get方法时,如果缓存中不存在对应的键值,LoadingCache会自动调用CacheLoader来加载数据,并将结果存入缓存。

第4章:创建LoadingCache

创建一个LoadingCache最关键的就是定义一个CacheLoader。这个CacheLoader指定了如何加载缓存。它就像是个工厂,当咱们请求的数据在缓存中不存在时,它就会生产出所需的数据。

那么,怎么定义这个CacheLoader呢?让小黑给你看个例子:

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

public class UserCache {
    // 假设有一个用户服务,用于获取用户信息
    private static UserService userService = new UserService();

    public static void main(String[] args) throws Exception {
        // 创建CacheLoader
        CacheLoader<String, User> loader = new CacheLoader<String, User>() {
            @Override
            public User load(String userId) {
                // 从用户服务获取用户信息
                return userService.getUserById(userId);
            }
        };

        // 创建LoadingCache
        LoadingCache<String, User> cache = CacheBuilder.newBuilder()
                .maximumSize(100) // 设置最大缓存数
                .build(loader);

        // 使用缓存获取用户信息
        User user = cache.get("123"); // 如果缓存中没有,会调用load方法加载数据
        System.out.println(user);
    }
}

在这个例子中,小黑创建了一个CacheLoader来从用户服务中获取用户信息。然后,使用CacheBuilder来构建一个LoadingCache,并设置了最大缓存数量为100。当咱们通过get方法获取用户信息时,如果缓存中没有相应的数据,CacheLoader就会自动加载数据。

这个过程听起来是不是很神奇?实际上,这背后是一种非常有效的数据管理策略。通过这种方式,咱们可以减少对数据库或远程服务的直接访问,提高了应用的响应速度和效率。

第5章:LoadingCache的高级特性

自动加载和刷新机制

首先,LoadingCache的一个很棒的功能就是自动加载和刷新。这意味着当咱们请求某个键的值时,如果这个值不存在或者需要刷新,LoadingCache会自动调用CacheLoader去加载或刷新数据。

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import java.util.concurrent.TimeUnit;

public class AutoRefreshCache {
    public static void main(String[] args) throws Exception {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .refreshAfterWrite(1, TimeUnit.MINUTES) // 设置1分钟后刷新
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) {
                        return fetchDataFromDatabase(key); // 模拟从数据库加载数据
                    }
                });

        // 使用缓存
        System.out.println(cache.get("key1")); // 第一次加载
        // 1分钟后,尝试再次获取,将触发刷新操作
    }

    private static String fetchDataFromDatabase(String key) {
        // 模拟数据库操作
        return "Data for " + key;
    }
}

在这个例子中,咱们设置了refreshAfterWrite,这意味着每当一个键值对写入一分钟后,它就会被自动刷新。

处理异常值

有时候,加载数据可能会出现异常。LoadingCache提供了优雅的处理异常的机制。

public class ExceptionHandlingCache {
    public static void main(String[] args) throws Exception {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        if ("errorKey".equals(key)) {
                            throw new Exception("Loading error");
                        }
                        return "Data for " + key;
                    }
                });

        try {
            System.out.println(cache.get("errorKey"));
        } catch (Exception e) {
            System.out.println("Error during cache load: " + e.getMessage());
        }
    }
}

这里,如果加载过程中出现异常,咱们可以捕获这个异常,并做适当的处理。

统计和监听功能

LoadingCache还提供了缓存统计和监听功能,这对于监控缓存性能和行为非常有用。

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;

public class CacheMonitoring {
    public static void main(String[] args) throws Exception {
        RemovalListener<String, String> removalListener = new RemovalListener<String, String>() {
            @Override
            public void onRemoval(RemovalNotification<String, String> notification) {
                System.out.println("Removed: " + notification.getKey() + ", Cause: " + notification.getCause());
            }
        };

        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(100)
                .removalListener(removalListener)
                .build(new CacheLoader<String, String>() {
                    // ...
                });

        // 使用缓存
        // ...
    }
}

在这个例子中,小黑设置了一个RemovalListener,用于监听缓存项的移除事件。

第6章:LoadingCache的最佳实践

配置缓存大小

合理配置缓存大小非常关键。如果缓存太小,就会频繁地加载数据,影响性能;如果太大,又可能消耗过多内存。

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .maximumSize(1000) // 设置最大缓存项为1000
        .build(new CacheLoader<String, String>() {
            // ...
        });

在这个例子中,小黑设置了最大缓存项为1000。这个值需要根据实际情况和资源限制来调整。

设置合适的过期策略

LoadingCache支持基于时间的过期策略,比如访问后过期和写入后过期。

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期
        .expireAfterAccess(5, TimeUnit.MINUTES) // 访问后5分钟过期
        .build(new CacheLoader<String, String>() {
            // ...
        });

选择合适的过期策略可以确保缓存中的数据既不会过时,又能有效利用内存。

异常处理策略

在加载数据时可能会遇到各种异常。咱们可以设置一个合理的异常处理策略,比如记录日志、返回默认值或者重新抛出异常。

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .build(new CacheLoader<String, String>() {
            @Override
            public String load(String key) throws Exception {
                try {
                    return fetchData(key);
                } catch (Exception e) {
                    // 处理异常
                }
            }
        });
使用软引用或弱引用

为了防止缓存占用过多内存,可以使用软引用或弱引用。

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .softValues() // 使用软引用存储值
        .weakKeys() // 使用弱引用存储键
        .build(new CacheLoader<String, String>() {
            // ...
        });

使用软引用和弱引用可以帮助Java垃圾收集器在需要时回收缓存项,防止内存泄露。

监听移除通知

设置移除监听器可以帮助咱们了解缓存的行为,比如为什么某个项被移除。

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .removalListener(notification -> {
            // 处理移除通知
        })
        .build(new CacheLoader<String, String>() {
            // ...
        });

通过这些最佳实践,咱们可以确保LoadingCache的高效运行,同时避免一些常见的问题。这样,咱们的Java应用就能更加稳定和高效地运行啦!

第7章:LoadingCache与Java 8的结合

好的,咱们接下来聊聊怎样把LoadingCache和Java 8的特性结合起来,用起来更顺手。

Java 8引入了很多强大的新特性,像Lambda表达式、Stream API等,这些都可以和LoadingCache搭配使用,让代码更简洁、更易读。

使用Lambda表达式简化CacheLoader

首先,咱们可以用Lambda表达式来简化CacheLoader的创建。这样代码看起来更干净,更直观。

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .build(key -> fetchDataFromDatabase(key)); // 使用Lambda表达式

private static String fetchDataFromDatabase(String key) {
    // 数据库操作
    return "Data for " + key;
}

在这个例子里,小黑用Lambda表达式替代了传统的匿名内部类,使代码更加简洁。

结合Stream API处理缓存数据

接下来,咱们看看如何用Java 8的Stream API来处理LoadingCache中的数据。

LoadingCache<String, User> userCache = //... 缓存初始化

List<String> userIds = //... 用户ID列表

// 使用Stream API获取用户信息列表
List<User> users = userIds.stream()
        .map(userId -> userCache.getUnchecked(userId))
        .collect(Collectors.toList());

在这个例子中,小黑用Stream API来处理一系列用户ID,然后用map方法从缓存中获取对应的用户信息。

利用Optional处理缓存返回值

最后,Java 8引入的Optional也可以用来优雅地处理可能为空的缓存返回值。

public Optional<User> getUser(String userId) {
    try {
        return Optional.ofNullable(userCache.get(userId));
    } catch (Exception e) {
        return Optional.empty();
    }
}

在这里,小黑用Optional包装了缓存的返回值。这样一来,就能优雅地处理缓存可能返回的空值情况。

通过这些方式,结合Java 8的特性,咱们可以让LoadingCache的使用更加高效和优雅。这不仅提高了代码的可读性,还让咱们的编程体验更加流畅。

第8章:实战案例

小黑将通过一个具体的例子,展示如何在实际项目中使用LoadingCache。这个例子会模拟一个简单的场景,比如说,使用LoadingCache来缓存用户的登录次数。

假设咱们有一个应用,需要跟踪用户的登录次数。每次用户登录时,程序会增加其登录次数。为了提高性能,咱们用LoadingCache来缓存这些数据,避免每次都查询数据库。

首先,小黑定义了一个模拟的用户登录服务:

public class UserService {
    private final Map<String, Integer> loginCount = new ConcurrentHashMap<>();

    public int addLoginCount(String userId) {
        return loginCount.merge(userId, 1, Integer::sum);
    }
}

UserService userService = new UserService();

这个UserService类有一个addLoginCount方法,用于增加特定用户的登录次数。

接下来,小黑将展示如何使用LoadingCache来缓存登录次数:

LoadingCache<String, Integer> loginCache = CacheBuilder.newBuilder()
        .expireAfterAccess(30, TimeUnit.MINUTES) // 设置缓存30分钟后过期
        .build(new CacheLoader<String, Integer>() {
            @Override
            public Integer load(String userId) {
                return userService.addLoginCount(userId);
            }
        });

public void userLogin(String userId) {
    int count = loginCache.getUnchecked(userId);
    System.out.println("User " + userId + " login count: " + count);
}

在这个例子中,每当有用户登录,userLogin方法就会被调用。这个方法会从loginCache中获取用户的登录次数,如果缓存中没有,CacheLoader会调用UserServiceaddLoginCount来获取最新的计数,然后将其存储在缓存中。

第9章:总结

通过这些章节,咱们了解了LoadingCache的基本原理和用法,包括如何创建和配置缓存,以及如何结合Java 8的特性来优化代码。LoadingCache不仅提供了自动加载和刷新的强大功能,还有异常处理、缓存统计和监听等高级特性。

实战案例给咱们展示了LoadingCache在现实场景中的应用。不管是缓存用户信息还是统计数据,LoadingCache都能大大提高性能和用户体验。

技术总是在不断进步的,学习和掌握这些工具,能让咱们更好地适应未来的技术挑战。希望这些内容能激发你对Guava以及Java编程的更多探索!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1336823.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何在Photoshop中创建色彩鲜艳的文本效果

如何在 Photoshop 中制作霓虹灯 1. 如何创建背景 步骤 1 学习如何在 Photoshop 中制作霓虹灯文本的第一步是背景。创建一个新的 1160 x 750 像素文档&#xff0c;并将分辨率设置为 300。 转到"文件">"嵌入位置"&#xff0c;然后打开"垃圾灰色砖…

苹果证书p12和描述文件的创建方法

​ 苹果证书p12和描述文件的创建方法 在2020年之前&#xff0c;我们在使用appuploder创建苹果证书的时候&#xff0c;只需要注册苹果开发者账号&#xff0c;但不需要缴费成为开发者。 在2020年之后&#xff0c;需要先缴费成为苹果开发者。 假如你还没有注册苹果开发者账号&…

ElasticSearch 文档操作

批量操作 语法 批量操作对json有严格的要求&#xff0c;每个json串不能换行&#xff0c;只能放在同一行&#xff0c;相邻的json串之间必须要有换行。每个操作必须是一对json串&#xff08;delete语法除外&#xff09; { action: { metadata }} { request body } { ac…

同步与互斥(三)

一、递归锁 /* 创建一个递归锁&#xff0c;返回它的句柄。 * 此函数内部会分配互斥量结构体 * 返回值: 返回句柄&#xff0c;非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );/* 释放 */ BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t…

WAVE SUMMIT+ 2023倒计时2天,传文心一言将曝最新进展!

10句话2分钟&#xff0c;挑战成功说服宿管阿姨开门&#xff0c;这个人群中的“显眼包”是一个接入文心大模型4.0游戏里的NPC&#xff0c;妥妥 “工具人”实锤&#xff5e; 尝试用AI一键自动识别好坏咖啡豆&#xff0c;看一眼便知好坏&#xff0c;真正“颜值即正义”&#xff0…

电脑监控软件排行榜(电脑监控软件隐藏安装)

在当今数字化时代&#xff0c;电脑已经成为我们工作、学习和生活中不可或缺的工具。然而&#xff0c;随着电脑使用的普及&#xff0c;电脑监控软件也逐渐浮出水面。这类软件可以对电脑进行全方位的监控和管理&#xff0c;保护电脑安全、提高工作效率。 本文将为您介绍电脑监控…

处理etcd源码包编译异常

1、下载etcd包&#xff0c;执行go build报异常&#xff1a; client\v2\example_keys_test.go:1:1: expected package, found . client\v3\example_auth_test.go:1:1: expected package, found . client\v3\concurrency\example_election_test.go:1:1: expected package, found…

限流(rate limiter)

项目中业务提出需求&#xff0c;要求对商品的立即购买接口进行限流。 经过百度及调研&#xff0c;决定在拦截器加限流。拦截器相关讲解见上几篇博客springmv中的拦截器 springmvc限流 RateLimiter 最终的限流代码如下(从百度借鉴&#xff0c;已经找不到出处&#xff09;&am…

Python模拟动态星空

前言 今天&#xff0c;我们来用Python做个星空。 一、模拟星空 1,.首先导入所需要的库&#xff1a; from turtle import * from random import random, randint 2.初始画面&#xff1a; screen Screen() width, height 800, 600 screen.setup(width, height) screen.tit…

【HTML】7个你可能不知道的HTML标签

在Web开发的广阔天地里&#xff0c;大部分开发人员像是在探索&#xff0c;需要掌握多种语言来开拓创新。而在这语言丛林中&#xff0c;学习一门语言的全部知识往往是一项巨大的挑战&#xff0c;有时候甚至会发现自己对一些看似普通但实则非常专业的标签知之甚少。 而网页开发中…

python的os模块详解

本章介绍python自带模块os&#xff0c;os为操作系统 operating system 的简写&#xff0c;意为python与电脑的交互。主要学习的函数有 os.getcwd()、os.chdir()、os.path.basename()、os.path.join()、os.path.exists()、os.path.isdir()、os.path.isfile()、os.listdir()、os.…

MyBatis 架构分析

文章目录 三层架构一、基础支撑层1.1 类型转换模块1.2 日志模块1.3 反射工具模块1.4 Binding 模块1.5 数据源模块1.6 缓存模块1.6 解析器模块1.7 事务管理模块 二、核心处理层2.1 配置解析2.2 SQL 解析与 scripting 模块。2.3 MyBatis 中的 scripting 模块就是负责动态生成 SQL…

spring初始化bean之后执行某个方法

这个问题可以分两种解释&#xff1a; 1. 某个bean初始化执行? 2. 所有bean初始化后执行? 第一个问题可以在spring bean的生命周期中找到答案&#xff1a; bean定义-实例化-初始化-销毁。注意&#xff1a; 这里的bean定义是指所有的bean定义完成&#xff0c;然后才继续执…

Plantuml之活动图语法介绍(二十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

MYSQL一一函数一一字符串函数

嘿嘿大家好我回来啦&#xff0c;今天我们要学习的是MYSQL中的函数&#xff0c;函数呢我们又分为字符串函数&#xff0c;数值函数&#xff0c;日期函数&#xff0c;流程函数来介绍&#xff0c;今天重点介绍字符串函数(从小题到案例方便你们更加深入的理解) 函数指的是一段可以直…

【unity学习笔记】4.场景切换

创建空物体→创建脚本挂载在空物体上→打开脚本 1.创建所需要的场景 assets中点击创建场景 2.文件→生成设置 3.将需要的场景拖入 4.场景跳转 创建空对象&#xff0c;将脚本放在空对象上。 注意两个类&#xff1a;场景类、场景管理类 void Start(){//场景跳转SceneManager.Lo…

论文阅读<CF-YOLO: Cross Fusion YOLO for Object Detection in Adverse Weather.....>

论文链接&#xff1a;https://arxiv.org/pdf/2309.08152.pdfhttps://arxiv.org/pdf/2206.01381.pdfhttps://arxiv.org/pdf/2309.08152.pdf 代码链接&#xff1a;https://github.com/DiffPrompter/diff-prompter 目前没有完整代码放出。 恶劣天气下的目标检测主要有以下三种解…

WIZnet Ethernet HAT评估版介绍

文章目录 1 简介2 硬件资源2.1 硬件规格2.2 引脚定义 3 参考资料3.1 Datasheet3.2 原理图3.3 尺寸图&#xff08;尺寸&#xff1a;mm&#xff09;3.4 参考例程 4 硬件协议栈优势 1 简介 WIZnet Ethernet HAT是一款可直接硬件附在树莓派RP2040Pico开发板上的兼容版&#xff0c;利…

格密码基础:垂直子空间与子格,q-ary垂直格

目录 一.写在前面 二.子空间垂直 2.1 理论解释 2.2 举例分析 三. 零空间 3.1 零空间与q-ary垂直格 3.2 零空间与行/列空间 四. 格密码相关 一.写在前面 格密码中的很多基础原语都来自于线性代数的基本概念&#xff0c;比如举几个例子&#xff1a; 格密码中的非满秩格…

差速器壳体形位误差测量系统圆柱度同轴度自动测量-CASAIM全自动尺寸测量仪

差速器壳体是汽车、拖拉机驱动系统的重要零件&#xff0c;其加工精度直接影响汽车、拖拉机等行走机构的使用寿命和行驶的安全性&#xff0c;因此&#xff0c;不仅对安装孔本身的尺寸精度要求高&#xff0c;而且对孔与孔的同轴度、孔与外圆的同轴度以及孔与端面的垂直度等位置公…