缓存 - Caffeine 不完全指北

news2024/11/29 4:44:30

文章目录

  • 官网
  • 概述
  • 设计
  • Code
    • POM
    • Population
    • Eviction Policy
    • Refresh
    • Statistics

在这里插入图片描述


官网

https://github.com/ben-manes/caffeine

wiki:

https://github.com/ben-manes/caffeine/wiki

在这里插入图片描述


概述

Caffeine是一个用于Java应用程序的高性能缓存框架。它提供了一个强大且易于使用的缓存库,可以在应用程序中使用,以提高数据访问的速度和效率。

下面是一些Caffeine缓存框架的主要特点:

  1. 高性能:Caffeine的设计目标之一是提供卓越的性能。它通过使用高效的数据结构和优化的算法来实现快速的缓存访问。与其他一些常见的缓存框架相比,Caffeine在缓存访问的速度和响应时间上表现出色。

  2. 内存管理:Caffeine提供了灵活的内存管理选项。它支持基于大小、基于数量和基于权重的缓存大小限制。你可以根据应用程序的需求来选择合适的缓存大小策略,并且可以通过配置参数进行进一步的调整。

  3. 强大的功能:Caffeine提供了许多强大的功能来满足各种需求。它支持异步加载和刷新缓存项,可以设置过期时间和定时刷新策略,支持缓存项的自动删除和手动失效等。此外,Caffeine还提供了统计信息和监听器机制,可以方便地监控和管理缓存的状态和变化。

  4. 线程安全:Caffeine是线程安全的,可以在多线程环境中安全地使用。它使用了细粒度的锁定机制来保护共享资源,确保并发访问的正确性和一致性。

  5. 易于集成:Caffeine是一个独立的Java库,可以很容易地与现有的应用程序集成。它与标准的Java并发库和其他第三方库兼容,并且可以与各种框架和技术(如Spring、Hibernate等)无缝集成。


Caffeine 是一个高性能的 Java 缓存框架,旨在提供快速、高效的内存缓存解决方案。它是由 Google 开发的,是 Guava 缓存的升级版。

Caffeine 的设计目标是提供高吞吐量、低延迟的缓存访问,并支持各种缓存策略和功能。以下是一些 Caffeine 框架的关键特点:

  1. 高性能:Caffeine 的设计优化了缓存的内存访问模式,使用了各种技术来减少缓存访问的开销,从而提高了性能。它使用了类似于 Java ConcurrentHashMap 的数据结构,支持并发访问,并提供了可配置的并发级别。

  2. 内存管理:Caffeine 提供了灵活的内存管理选项,可以通过设置缓存的最大大小、最大条目数或最大权重来控制缓存的大小。它还支持基于容量、时间或引用等策略来自动清理过期的缓存条目。

  3. 强大的缓存策略:Caffeine 提供了多种缓存策略,包括基于访问时间、写入时间或自定义规则的过期策略。它还支持最近最少使用(LRU)、最近最不常用(LFU)和固定大小等其他策略。

  4. 异步加载:Caffeine 支持异步加载缓存条目的功能。当缓存中不存在所需的条目时,它可以自动触发加载过程,并在加载完成后将结果放入缓存。

  5. 统计和监控:Caffeine 提供了丰富的统计和监控功能,可以跟踪缓存的命中率、加载时间、缓存大小等指标。这些信息对于调优和性能分析非常有用。

  6. 扩展性:Caffeine 的设计允许开发人员通过自定义策略、缓存加载器和监听器等扩展框架的功能。

使用 Caffeine 缓存框架非常简单。你可以通过以下步骤来开始使用:

  1. 引入 Caffeine 依赖:在你的项目中添加 Caffeine 的依赖项,可以通过 Maven、Gradle 或直接下载 JAR 文件进行引入。

  2. 创建缓存实例:使用 Caffeine 的构建器模式创建一个缓存实例,可以设置缓存的参数和策略。

  3. 存储和获取数据:使用缓存的 put 方法将数据存储到缓存中,使用 get 方法从缓存中获取数据。如果缓存中不存在所需的数据,可以选择触发异步加载或提供自定义加载逻辑。

  4. 调优和配置:根据应用程序的需求,可以调整缓存的参数和策略,以获得最佳的性能和内存管理。

总之,Caffeine 是一个功能强大、高性能的 Java 缓存框架,适用于各种应用场景,尤其是需要快速访问、低延迟和高吞吐量的内存缓存需求。


设计

在这里插入图片描述


Code

POM

<dependency>
  <groupId>com.github.ben-manes.caffeine</groupId>
   <artifactId>caffeine</artifactId>
   <!-- Java 8 users can continue to use version 2.x, which will be supported -->
   <version>2.9.3</version>
</dependency>

如果是caffeine 3.x的版本,需要JDK 11 以上。


Population

https://github.com/ben-manes/caffeine/wiki/Population

package com.artisan.caffeinedemo.java;

import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import lombok.Builder;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Slf4j
public class CaffeineBaseExampleWithJava {


    @SneakyThrows
    public static void main(String[] args) {
        //  initFirstWay();
        //  initSecondWay();
        initThirdWay();

        TimeUnit.SECONDS.sleep(10);

    }


    /**
     * Cache手动创建
     * <p>
     * 最普通的一种缓存,无需指定加载方式,需要手动调用put()进行加载。需要注意的是put()方法对于已存在的key将进行覆盖,这点和Map的表现是一致的。
     * 在获取缓存值时,如果想要在缓存值不存在时,原子地将值写入缓存,则可以调用get(key, k -> value)方法,该方法将避免写入竞争。调用invalidate()方法,将手动移除缓存。
     * <p>
     * 在多线程情况下,当使用get(key, k -> value)时,如果有另一个线程同时调用本方法进行竞争,则后一线程会被阻塞,直到前一线程更新缓存完成;
     * 而若另一线程调用getIfPresent()方法,则会立即返回null,不会被阻塞。
     */
    @SneakyThrows
    public static void initFirstWay() {

        Cache<Object, Object> cache = Caffeine.newBuilder()
                //初始数量
                .initialCapacity(10)
                //最大条数
                .maximumSize(10)
                //expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准
                //最后一次写操作后经过指定时间过期
                .expireAfterWrite(3, TimeUnit.SECONDS)
                //最后一次读或写操作后经过指定时间过期
                .expireAfterAccess(3, TimeUnit.SECONDS)
                //监听缓存被移除
                .removalListener((key, val, removalCause) -> {
                    log.info("listener remove : {} ,{} ,{}", key, val, removalCause);
                })
                //记录命中
                .recordStats()
                .build();

        cache.put("name", "小工匠");
        //小工匠
        log.info((String) cache.getIfPresent("name"));

        // 结合初始化的时候设置的过期时间, 模拟程序运行5秒后,再此获取
        TimeUnit.SECONDS.sleep(5);
        log.info("5秒后再次获取:{}", (String) cache.getIfPresent("name"));


        //存储的是默认值
        log.info((String) cache.get("noKey", o -> "默认值"));

    }


    /**
     * Loading Cache自动创建
     * <p>
     * LoadingCache是一种自动加载的缓存。其和普通缓存不同的地方在于,当缓存不存在/缓存已过期时,若调用get()方法,则会自动调用CacheLoader.load()方法加载最新值。
     * 调用getAll()方法将遍历所有的key调用get(),除非实现了CacheLoader.loadAll()方法。
     * <p>
     * 使用LoadingCache时,需要指定CacheLoader,并实现其中的load()方法供缓存缺失时自动加载。
     * <p>
     * 在多线程情况下,当两个线程同时调用get(),则后一线程将被阻塞,直至前一线程更新缓存完成。
     */
    @SneakyThrows
    public static void initSecondWay() {

        Map<Integer, Artisan> map = getArtisanMap();


        log.info("size:{}", map.size());

        LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
                //创建缓存或者最近一次更新缓存后经过指定时间间隔,刷新缓存;refreshAfterWrite仅支持LoadingCache
                .refreshAfterWrite(10, TimeUnit.SECONDS)
                .expireAfterWrite(10, TimeUnit.SECONDS)
                .expireAfterAccess(10, TimeUnit.SECONDS)
                .maximumSize(10)
                //根据key查询数据库里面的值,这里是个lambda表达式
                // TODO  根据其概况初始化 ,比如   build(userId -> getUserFromDatabase(userId));
                //   private User getUserFromDatabase(String userId) {
                //    // 这里会从数据库查询用户信息
                //    // ...
                //    return user;
                //  }
                .build(key -> map.get(Integer.parseInt(key)).toString());


        // 当执行get的时候,会触发 build 中的 lambda表达式
        log.info(loadingCache.get("1"));
        log.info(loadingCache.get("2"));
        log.info(loadingCache.get("3"));
        log.info(loadingCache.get("4"));
        log.info(loadingCache.get("4", o -> "默认值"));

        // 获取一个不存在的值
        log.info(loadingCache.get("noKey", o -> "默认值"));
    }


    /**
     * AsyncCache是Cache的一个变体,其响应结果均为CompletableFuture,
     * 通过这种方式,AsyncCache对异步编程模式进行了适配。
     * <p>
     * 默认情况下,缓存计算使用ForkJoinPool.commonPool()作为线程池,如果想要指定线程池,则可以覆盖并实现Caffeine.executor(Executor)方法。
     * <p>
     * synchronous()提供了阻塞直到异步缓存生成完毕的能力,它将以Cache进行返回。
     * <p>
     * 在多线程情况下,当两个线程同时调用get(key, k -> value),则会返回同一个CompletableFuture对象。由于返回结果本身不进行阻塞,可以根据业务设计自行选择阻塞等待或者非阻塞。
     */
    @SneakyThrows
    public static void initThirdWay() {

        Map<Integer, Artisan> map = getArtisanMap();


        log.info("size:{}", map.size());


        AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
                //创建缓存或者最近一次更新缓存后经过指定时间间隔刷新缓存;仅支持LoadingCache
                .refreshAfterWrite(1, TimeUnit.SECONDS)
                .expireAfterWrite(1, TimeUnit.SECONDS)
                .expireAfterAccess(1, TimeUnit.SECONDS)
                .maximumSize(10)
                //根据key查询数据库里面的值
                .buildAsync(key -> {
                    Thread.sleep(1000);
                    return map.get(Integer.parseInt(key)).toString();
                });

        //异步缓存返回的是CompletableFuture
        CompletableFuture<String> future = asyncLoadingCache.get("1");
        future.thenAccept(System.out::println);

    }

    private static Map<Integer, Artisan> getArtisanMap() {
        Map<Integer, Artisan> map = new HashMap<>(16);
        map.put(1, Artisan.builder().id(1).name("artisan1").hobbies(Arrays.asList("Java")).build());
        map.put(2, Artisan.builder().id(2).name("artisan2").hobbies(Arrays.asList("AIGC")).build());
        map.put(3, Artisan.builder().id(3).name("artisan3").hobbies(Arrays.asList("HADOOP")).build());
        map.put(4, Artisan.builder().id(4).name("artisan4").hobbies(Arrays.asList("GO")).build());
        return map;
    }

    @Data
    @Builder
    private static class Artisan {
        private Integer id;
        private String name;
        private List<String> hobbies;
    }
}
    

Eviction Policy

https://github.com/ben-manes/caffeine/wiki/Eviction

package com.artisan.caffeinedemo.java;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Scheduler;
import com.github.benmanes.caffeine.cache.Weigher;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 * @desc: 驱逐策略在创建缓存的时候进行指定。常用的有基于容量的驱逐和基于时间的驱逐。
 * <p>
 * 基于容量的驱逐需要指定缓存容量的最大值,当缓存容量达到最大时,Caffeine将使用LRU策略对缓存进行淘汰;基于时间的驱逐策略如字面意思,可以设置在最后访问/写入一个缓存经过指定时间后,自动进行淘汰。
 * <p>
 * 驱逐策略可以组合使用,任意驱逐策略生效后,该缓存条目即被驱逐。
 * <p>
 * LRU 最近最少使用,淘汰最长时间没有被使用的页面。
 * LFU 最不经常使用,淘汰一段时间内使用次数最少的页面
 * FIFO 先进先出
 * Caffeine有4种缓存淘汰设置
 * <p>
 * 大小 (LFU算法进行淘汰)
 * 权重 (大小与权重 只能二选一)
 * 时间
 * 引用
 */
@Slf4j
public class CaffeineEvictionPolicy {

    /**
     * 缓存大小淘汰
     */
    @Test
    public void maximumSizeTest() throws InterruptedException {
        Cache<Integer, Integer> cache = Caffeine.newBuilder()
                //超过10个后会使用W-TinyLFU算法进行淘汰
                .maximumSize(10)
                .evictionListener((key, val, removalCause) -> {
                    log.info("淘汰缓存:key:{} val:{}", key, val);
                })
                .build();

        // 模拟写入数据
        for (int i = 1; i < 20; i++) {
            cache.put(i, i);
        }

        //缓存淘汰是异步的
        Thread.sleep(1000);

        // 打印还没被淘汰的缓存
        log.info("未淘汰的缓存:{}", cache.asMap());
    }

    /**
     * 权重淘汰
     */
    @Test
    public void maximumWeightTest() throws InterruptedException {
        Cache<Integer, Integer> cache = Caffeine.newBuilder()
                //限制总权重,若所有缓存的权重加起来>总权重就会淘汰权重小的缓存
                .maximumWeight(100)
                .weigher((Weigher<Integer, Integer>) (key, value) -> key)
                .evictionListener((key, val, removalCause) -> {
                    log.info("淘汰缓存:key:{} val:{}", key, val);
                })
                .build();

        //总权重其实是=所有缓存的权重加起来
        int maximumWeight = 0;
        for (int i = 1; i < 20; i++) {
            cache.put(i, i);
            maximumWeight += i;
        }
        log.info("总权重={}", maximumWeight);

        //缓存淘汰是异步的
        Thread.sleep(1000);

        // 打印还没被淘汰的缓存
        log.info("未淘汰的缓存:{}", cache.asMap());
    }


    /**
     * 访问后到期(每次访问都会重置时间,也就是说如果一直被访问就不会被淘汰)
     */
    @Test
    public void expireAfterAccessTest() throws InterruptedException {
        Cache<Integer, Integer> cache = Caffeine.newBuilder()
                .expireAfterAccess(1, TimeUnit.SECONDS)
                //可以指定调度程序来及时删除过期缓存项,而不是等待Caffeine触发定期维护
                //若不设置scheduler,则缓存会在下一次调用get的时候才会被动删除
                .scheduler(Scheduler.systemScheduler())
                .evictionListener((key, val, removalCause) -> {
                    log.info("淘汰缓存:key:{} val:{}", key, val);

                })
                .build();
        cache.put(1, 2);

        log.info("{}", cache.getIfPresent(1));

        Thread.sleep(5000);

        //null
        log.info("{}", cache.getIfPresent(1));

        Thread.sleep(500);
    }

    /**
     * 写入后到期
     */
    @Test
    public void expireAfterWriteTest() throws InterruptedException {
        Cache<Integer, Integer> cache = Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.SECONDS)
                //可以指定调度程序来及时删除过期缓存项,而不是等待Caffeine触发定期维护
                //若不设置scheduler,则缓存会在下一次调用get的时候才会被动删除
                .scheduler(Scheduler.systemScheduler())
                .evictionListener((key, val, removalCause) -> {
                    log.info("淘汰缓存:key:{} val:{}", key, val);
                })
                .build();
        cache.put(1, 2);

        Thread.sleep(3000);

        //null
        log.info("{}", cache.getIfPresent(1));
    }


}
    

Refresh

https://github.com/ben-manes/caffeine/wiki/Refresh

package com.artisan.caffeinedemo.java;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 * <p>
 * <p>
 * refreshAfterWrite()表示x秒后自动刷新缓存的策略可以配合淘汰策略使用,
 * <p>
 * 注意的是刷新机制只支持LoadingCache和AsyncLoadingCache
 */
@Slf4j
public class CaffeineRefreshPolicy {

    private static int NUM = 0;

    @Test
    public void refreshAfterWriteTest() throws InterruptedException {
        LoadingCache<Integer, Integer> cache = Caffeine.newBuilder()
                .refreshAfterWrite(1, TimeUnit.SECONDS)
                //模拟获取数据,每次获取就自增1
                .build(integer -> ++NUM);

        //获取ID=1的值,由于缓存里还没有,所以会自动放入缓存
        // 返回结果 1
        log.info("get value = {}", cache.get(1));

        // 延迟2秒后,理论上自动刷新缓存后取到的值是2
        // 但其实不是,值还是1,因为refreshAfterWrite并不是设置了n秒后重新获取就会自动刷新
        // 而是x秒后&&第二次调用getIfPresent的时候才会被动刷新
        Thread.sleep(2000);
        // 返回结果 1
        log.info("get value = {}", cache.getIfPresent(1));

        //此时才会刷新缓存,而第一次拿到的还是旧值 ,这时候拿到的就是新的值了 2
        // 返回结果2
        log.info("get value = {}", cache.getIfPresent(1));
    }

}
    

Statistics

https://github.com/ben-manes/caffeine/wiki/Statistics

package com.artisan.caffeinedemo.java;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Slf4j
public class CaffeineStatstic {


    @Test
    public void testStatistic() {

        LoadingCache<String, String> cache = Caffeine.newBuilder()
                //创建缓存或者最近一次更新缓存后经过指定时间间隔,刷新缓存;refreshAfterWrite仅支持LoadingCache
                .refreshAfterWrite(1, TimeUnit.SECONDS)
                .expireAfterWrite(1, TimeUnit.SECONDS)
                .expireAfterAccess(1, TimeUnit.SECONDS)
                .maximumSize(10)
                //开启记录缓存命中率等信息
                .recordStats()
                //根据key查询数据库里面的值
                .build(key -> {
                    Thread.sleep(1000);
                    return new Date().toString();
                });


        cache.put("1", "shawn");
        cache.get("1");

        /*
         * hitCount :命中的次数
         * missCount:未命中次数
         * requestCount:请求次数
         * hitRate:命中率
         * missRate:丢失率
         * loadSuccessCount:成功加载新值的次数
         * loadExceptionCount:失败加载新值的次数
         * totalLoadCount:总条数
         * loadExceptionRate:失败加载新值的比率
         * totalLoadTime:全部加载时间
         * evictionCount:丢失的条数
         */
        log.info("统计信息如下:\n {}", cache.stats());
    }
}
    

在这里插入图片描述

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

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

相关文章

如何判定是否一份适合工作呢

核心指标&#xff1a;喜欢 春节仿佛还在昨天&#xff0c;转眼间2023年已经过半。分享和总结一下自己过去的这6个月吧&#xff01;你可以从以下几个方面展开谈谈。 23年上半年已经过去啦。结合工作多年经历简单写一写。 主要写自己&#xff1a; 工作非常努力&#xff0c;但是…

Java 一文掌握全部阻塞队列的使用

1、简介 本文主要对Java常用阻塞队列进行介绍和提供相关使用案例 2、 阻塞队列作用 阻塞队列提供了一种线程安全、高效的数据传递和同步机制 &#xff0c; 主要用于缓冲数据、限流、削峰填谷&#xff0c;生产者-消费者模型&#xff0c;线程间的协作等等。 3、 各阻塞队列区…

人工智能学术顶会——NeurIPS 2022 议题(网络安全方向)清单、摘要与总结

按语&#xff1a;随着大模型的崛起&#xff0c;将AI再次推向一个高峰&#xff0c;受到的关注也越来越大。在网络安全领域&#xff0c;除4大安全顶会外&#xff0c;一些涉及AI的安全话题&#xff0c;包括对AI的攻防研究&#xff0c;以及应用AI做安全的研究方向&#xff0c;也会发…

在vite创建的vue3项目中使用Cesium加载纽约建筑模型、设置样式,划分城市区域并着色

在vite创建的vue3项目中使用Cesium加载纽约建筑模型、设置样式&#xff0c;划分城市区域并着色 使用vite创建vue3项目 npm create vitelatestcd到创建的项目文件夹中 npm install安装Cesium npm i cesium vite-plugin-cesium vite -D配置 vite.config.js文件&#xff1a;添加Ce…

系统架构设计师 8:系统质量属性与架构评估

软件系统属性包括功能属性和质量属性&#xff0c;软件架构重点关注的是质量属性。为了精确、定量地表达系统的质量属性&#xff0c;通常会采用质量属性场景的方式进行描述。 在确定软件系统架构&#xff0c;精确描述质量属性场景后&#xff0c;就需要对系统架构进行评估。软件…

前端|CSS(二)

参考视频&#xff1a;黑马程序员前端CSS3基础教程&#xff0c;前端必备基础 目录 &#x1f4da;CSS 布局的三种机制 &#x1f407;普通流 &#x1f407;浮动 ⭐️浮动介绍 ⭐️浮动(float)的应用 ⭐️浮动(float)的扩展 ⭐️清除浮动 &#x1f407;定位 ⭐️定位 ⭐️…

shell? 变量!

目录 ​编辑 &#x1f428;什么是shell &#x1f428;编译型语言和解释型语言 &#x1f428;解释型语言 &#x1f428;变量 &#x1f428;1.局部变量&#xff1a; &#x1f428;2.环境变量通常又称“全局变量” &#x1f428;3.设置环境变量&#xff1a; &#x1f4…

NZ系列工具:NZ11:VBA光标跟随策略

【分享成果&#xff0c;随喜正能量】生活就像是一杯苦茶&#xff0c;而情感是茉莉花&#xff0c;调兑在一起&#xff0c;才会馥郁芬芳。人活在世上&#xff0c;有诸多苦楚萦心&#xff0c;若不懂得自我调解&#xff0c;终究会被纷呈的世相掩埋。所以&#xff0c;更多的时候&…

Ubuntu18.04修改file descriptors(文件描述符限制),解决elasticsearch启动报错问题

最近在学习elasticsearch&#xff0c;使用的平台是Ubuntu18.04&#xff0c;在部署过程中的坑记录一下。 下载安装的过程就不说了&#xff0c;在启动es的时候报错 1 max file descriptors [4096] for elasticsearch process is too low, increase to at least [65535] 看了下…

TIOBE 7月编程语言榜出炉,这个语言强势突围,前三发生重大变化

TIOBE 公布了 2023 年 7 月的编程指数信息&#xff0c;在这个月&#xff0c;语言榜有什么新变化&#xff0c;让我们一起去看看吧&#xff01; JavaScript 创历史新高 几个月前&#xff0c;编程语言 C 占据了 TIOBE 指数的第 3 位&#xff08;超过了 Java&#xff09;。 但C的…

Golang 中的流程控制

1、Golang 中的流程控制 Go 语言中最常用的流程控制有 if 和 for &#xff0c;而 switch 和 goto 主要是为了简化代码、降低重复 代码而生的结构&#xff0c;属于扩展类的流程控制。 2、if else(分支结构) if 语句 if 语句 由一个布尔表达式后紧跟一个或多个语句组成。…

JDBC技术【SQL注入、JDBC批量添加数据、JDBC事务处理、其他查询方式】(三)-全面详解(学习总结---从入门到深化)

目录 SQL注入 JDBC批量添加数据 JDBC事务处理 Blob类型的使用 插入Blob类型数据 其他查询方式 动态条件查询 SQL注入 什么是SQL注入 所谓 SQL 注入&#xff0c;就是通过把含有 SQL 语句片段的参数插入到需要 执行的 SQL 语句中&#xff0c; 最终达到欺骗数据库服务器执行…

如何将各个阶段的数据进行对比?Sugar BI 教你快速搞定

折线图可以将当前和某个时间段的数据进行对比&#xff0c;比如前一天、上周、去年。 数据对比开启条件 当折线图 X 轴有且只有一个日期或时间字段&#xff0c;并且聚合方式为年-xx&#xff0c;Y 轴有且只有一个度量字段时&#xff0c;可以开启并配置折线图数据对比。 支持数据…

VMware使用ubuntu虚拟机的一些使用技巧

VMware安装Ubuntu虚拟机一般相对比较容易&#xff0c;本文记录一些VMware使用ubuntu虚拟机的其他使用技巧。 一、Ubuntu共享文件夹 1.1、 挂载镜像文件&#xff1a; 虚拟机->设置->硬件->CD/DVD.右边“连接”下面选择“使用IOS镜像文件”&#xff0c;浏览选择虚拟机…

【独家揭秘】微信居然可以自动通过好友申请并自动打招呼啦!

最近有客户来咨询&#xff0c;说是因为做内容引流到微信&#xff0c;所以每天很多人加她&#xff0c;微信都快被加爆了&#xff0c;每天手动通过好友申请和打招呼&#xff0c;回答了很多一模一样的问题&#xff0c;就一个小时已经让她很疲惫了&#xff0c;很机械的重复这些事。…

caj文件怎么转成pdf文件格式?分享两个免费方法!

在数字化的世界中&#xff0c;文件格式转换是我们日常生活和工作中常见的需求。CAJ文件是中国学术文献网络出版总库使用的一种文件格式&#xff0c;而PDF是全球广泛接受的文件格式&#xff0c;具有良好的兼容性和稳定性。本文将介绍两种免费的方法&#xff0c;帮助你将CAJ文件转…

细节:双花括号({{ ... }})在Vue.js中的用法

问题&#xff1a; 为什么后端返回的是数字类型时&#xff0c; {{ form.orderPrice }}可以拿到值展示&#xff0c; {{ form.orderPrice || "-" }} 不可以&#xff1f; 接口返回数据&#xff1a; <el-form-item label"订单金额&#xff1a;" prop"…

2.0 熟悉CheatEngine修改器

Cheat Engine 一般简称为CE&#xff0c;它是一款功能强大的开源内存修改工具&#xff0c;其主要功能包括、内存扫描、十六进制编辑器、动态调试功能于一体&#xff0c;且该工具自身附带了脚本工具&#xff0c;可以用它很方便的生成自己的脚本窗体&#xff0c;CE工具可以帮助用户…

shader学习记录——彩色光圈

参考连接 https://blog.csdn.net/stalendp/article/details/21993227 Shader "Custom/ColorRingShader" {Properties{_MainTex ("Texture", 2D) "white" {}}SubShader{Tags { "RenderType""Opaque" }LOD 100Pass{CGPROGRA…

fastapi docs打开为空白解决办法

空白的原因 使用的cdn为国外cdn 解决办法 使用国内cdn 解决步骤 1.打开此文件D:\Program Files\Python\Lib\site-packages\fastapi\openapi\docs.py 2.修改cdn地址 国内cdn不好找呀 &#xff08;1&#xff09;.七牛云存储 开放静态文件CDN&#xff0c;地址&#xff1a;h…