Redis实战(黑马点评)——关于缓存(缓存更新策略、缓存穿透、缓存雪崩、缓存击穿、Redis工具)

redis实现查询缓存的业务逻辑 

 service层实现

@Override
    public Result queryById(Long id) {
        String key = CACHE_SHOP_KEY + id;
        // 现查询redis内有没有数据
        String shopJson = (String) redisTemplate.opsForValue().get(key);
        if(StrUtil.isNotBlank(shopJson)){ // 如果redis的数据为存在,那么解析为对象
            // 将json转为对象
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        // 如果不存在,就先查数据库,再存入redis
        Shop shop = getById(id);
        if(shop == null){
            return Result.fail("店铺不存在");
        }
        // 存在就写入redis,包括将对象转为json
        redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
        return Result.ok(shop);
    }

缓存更新策略 

缓存更新策略的最佳实践方案:

  1. 低一致性需求:使用Redis自带的内存淘汰机制
  2. 高一致性需求:主动更新,并以超时删除作为处理方案
读操作:(查询)
  • 缓存命中则直接返回
  • 缓存未命中则查询数据库,并写入缓存,设定超时时间
写操作:(增删改)
  • 先写数据库,然后再删除缓存
  • 要确保数据库与缓存操作的原子性

缓存穿透

缓存穿透产生的原因是什么?

  • 用户请求的数据在缓存中和数据库中都不存在,不断发起这样的请求,给数据库带来巨大压力。

缓存穿透的解决方案有哪些?

  • 缓存null值
  • 布隆过滤器
  • 增强id的复杂度,避免被猜测id规律
  • 做好数据的基本格式校验
  • 加强用户权限校验
  • 做好热点参数的限流

 缓存空对象的方法解决缓存穿透

@Override  
public Result queryById(Long id) {  
    // 使用店铺ID构建缓存键  
    String key = CACHE_SHOP_KEY + id;  

    // 检查店铺信息是否已经缓存到Redis中  
    String shopJson = (String) redisTemplate.opsForValue().get(key);  
    
    // 如果缓存中存在数据,则将JSON字符串解析为Shop对象  
    if (StrUtil.isNotBlank(shopJson)) {   
        // 将JSON转换为Shop对象  
        Shop shop = JSONUtil.toBean(shopJson, Shop.class);  
        return Result.ok(shop); // 返回店铺对象作为成功结果  
    }  
    
    // 如果缓存中包含表示无数据的占位符,则返回错误信息  
    if ("#".equals(shopJson)) {  
        return Result.fail("没有店铺相关信息"); // 没有店铺信息可用  
    }  

    // 如果缓存中未找到店铺数据,则查询数据库  
    Shop shop = getById(id);  
    
    // 如果数据库中不存在该店铺  
    if (shop == null) {  
        // 在缓存中存储一个占位符,以表示该店铺不存在  
        // 这可以防止对同一ID的进一步查询再次访问数据库  
        redisTemplate.opsForValue().set(key, "#", CACHE_NULL_TTL, TimeUnit.MINUTES);  
        return Result.fail("店铺不存在"); // 返回错误,指示店铺不存在  
    }  
    
    // 如果找到店铺,则将店铺对象以JSON字符串的形式存储到缓存中  
    redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);  
    return Result.ok(shop); // 返回店铺对象作为成功结果  
}

缓存雪崩

缓存击穿 

缓存击穿是指在高并发环境下,某个热点数据的缓存同时失效,导致大量请求直接访问数据库,从而造成数据库压力骤增的现象。

缓存击穿解决方案

解决方案优点缺点
互斥锁

- 没有额外的内存消耗

- 保证一致性

- 实现简单

- 线程需要等待,性能受到影响

- 可能有死锁风险

逻辑锁- 线程无需等待,性能较好

- 不保证一致性

- 有额外的内存消耗

- 实现复杂

基于互斥锁解决缓存击穿 

 基于互斥锁解决缓存击穿 + 缓存空对象的方法解决缓存穿透

// 查询店铺信息,使用互斥锁解决缓存击穿问题
public Shop queryWithMutex(Long id) {
    // 构造缓存的key
    String key = CACHE_SHOP_KEY + id;

    // 1. 先从Redis中查询店铺信息
    String shopJson = (String) redisTemplate.opsForValue().get(key);

    // 如果Redis中存在缓存数据,直接解析JSON并返回对象
    if (StrUtil.isNotBlank(shopJson)) {
        // 将JSON字符串转换为Shop对象
        Shop shop = JSONUtil.toBean(shopJson, Shop.class);
        return shop;
    }

    // 如果Redis中缓存的值为"#", 表示数据库中没有该店铺信息
    if ("#".equals(shopJson)) {
        return null;
    }

    // 2. 构造互斥锁的key
    String lockKey = "lock:shop:" + id;

    // 定义店铺对象
    Shop shop = null;

    try {
        // 尝试获取互斥锁
        boolean isLock = tryLock(lockKey);

        // 如果获取锁失败,线程休眠50ms后重试
        if (!isLock) {
            Thread.sleep(50); // 等待50ms
            return queryWithMutex(id); // 递归调用,再次尝试获取锁
        }

        // 3. 如果没有获取到缓存数据,查询数据库
        shop = getById(id);

        // 模拟数据库查询的延时,生产环境应该去掉这段代码
        // Thread.sleep(200);

        // 4. 如果数据库中没有该店铺信息
        if (shop == null) {
            // 在Redis中存储一个特殊的标记值"#", 表示该店铺不存在
            redisTemplate.opsForValue().set(key, "#", CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }

        // 5. 如果数据库中有数据,将店铺信息存入Redis
        // 将Shop对象转换为JSON字符串
        redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
    } catch (InterruptedException e) {
        // 捕获线程中断异常
        throw new RuntimeException(e);
    } finally {
        // 释放互斥锁
        unlock(lockKey);
    }

    // 返回查询到的店铺信息
    return shop;
}

// 封装获取锁,释放锁
// 尝试获取互斥锁
private boolean tryLock(String key) {
    // 使用Redis的setIfAbsent方法尝试设置锁
    // 如果key不存在,则设置成功并返回true;否则返回false
    Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
    // 并设置一个过期时间(这里是 10 秒)
    return BooleanUtil.isTrue(flag);
}

// 释放互斥锁
private void unlock(String key) {
    // 删除锁对应的key
    redisTemplate.delete(key);
}

setIfAbsent 方法是 Redis 中的一种操作,用于设置一个键的值,仅在该键不存在的情况下进行设置。具体来说,它的功能如下:

  • 键不存在时:如果指定的键(key)在 Redis 中不存在,则将其设置为指定的值(在这个例子中是 "1"),并可以指定该键的过期时间(这里是 10 秒)。此时,方法返回 true

  • 键已存在时:如果指定的键已经存在于 Redis 中,则不会进行任何操作,保持原有的值不变,方法返回 false

基于逻辑锁解决缓存击穿 

 // 创建一个固定大小的线程池,用于缓存重建任务,避免频繁创建线程带来的开销
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    /**
     * 查询商铺信息,考虑逻辑过期
     * @param id 商铺ID
     * @return 商铺信息,如果不存在或已过期则返回null
     */
    public Shop queryWithLogicalExpire(Long id){
        // 构建缓存的key,用于从Redis中查询对应的商铺信息
        String key = CACHE_SHOP_KEY + id;
        // 1. 从Redis查询商铺缓存,获取商铺信息的JSON字符串
        String shopJson = (String) redisTemplate.opsForValue().get(key);
        // 2. 判断缓存是否存在
        if (StrUtil.isBlank(shopJson)) {
            // 3. 缓存不存在,直接返回null
            return null;
        }
        // 4. 缓存命中,需要先将JSON字符串反序列化为RedisData对象
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        // 从RedisData对象中提取商铺信息,并将其反序列化为Shop对象
        Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
        // 获取缓存的过期时间
        LocalDateTime expireTime = redisData.getExpireTime();
        // 5. 判断缓存是否过期
        if (expireTime.isAfter(LocalDateTime.now())) {
            // 5.1. 缓存未过期,直接返回商铺信息
            return shop;
        }
        // 构建锁的key,用于控制缓存重建的并发访问
        String lockKey = LOCK_SHOP_KEY + id;
        // 尝试获取锁,确保缓存重建操作的线程安全
        boolean isLock = tryLock(lockKey);
        // 6.2. 判断是否成功获取锁
        if (isLock) {
            // 6.3. 成功获取锁,开启独立线程进行缓存重建,避免阻塞主线程
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    // 重建缓存,将最新的商铺信息保存到Redis中,并设置过期时间
                    this.saveShop2Redis(id, 20L); // 假设20L是过期时间,单位为秒
                } catch (Exception e) {
                    // 如果在缓存重建过程中发生异常,抛出运行时异常,并记录日志
                    throw new RuntimeException(e);
                } finally {
                    // 无论缓存重建成功与否,都需要释放锁,避免死锁
                    unlock(lockKey);
                }
            });
        }
        // 返回当前查询到的商铺信息(可能已过期)
        return shop;
    }

    public void saveShop2Redis(Long id, Long expireSeconds) {
        // 1. 查询店铺数据
        Shop shop = getById(id);
        // 2. 封装逻辑过期时间
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        // 3. 写入Redis
        redisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
    }

RedisData类

@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}

 封装redis工具类

    @Resource
    private CacheClient cacheClient;

    public static final String CACHE_SHOP_KEY = "cache:shop:";
    public static final Long CACHE_SHOP_TTL = 30L;
    @Override
    public Result queryById(Long id)  {
        // 解决缓存穿透
//        Shop shop = cacheClient // 传入一个从数据库内获取Shop对象的函数:this::getById
//                .queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);
        
        
        // 互斥锁解决缓存击穿
        Shop shop = cacheClient
                .queryWithMutex(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);


        // 逻辑过期解决缓存击穿
//        Shop shop = cacheClient
//                .queryWithLogicalExpire(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);
       
         if(shop == null){
            return Result.fail("店铺不存在");
        }
        return Result.ok(shop);
    }

1. queryWithPassThrough

这个方法用于处理缓存穿透问题。缓存穿透是指查询一个数据库中不存在的数据,由于缓存中也没有这个数据,所以每次查询都会直接打到数据库上,增加数据库的压力。

  • 参数

    • keyPrefix:缓存的前缀。

    • id:缓存的ID。

    • type:返回对象的类型。

    • dbFallback:数据库查询的回调函数。

    • time:缓存时间。

    • unit:时间单位。

2. queryWithLogicalExpire

这个方法用于处理缓存击穿问题。缓存击穿是指一个缓存中非常热门的数据突然过期,导致大量请求同时打到数据库上,增加数据库的压力。

  • 参数:与 queryWithPassThrough 相同。

最大的缺点是运行前要把所有缓存加到redis内,不然怎么查都是null

3. queryWithMutex(互斥锁)

这个方法结合了 queryWithPassThroughqueryWithLogicalExpire 的功能,用于处理缓存穿透和缓存击穿问题。

  • 参数:与 queryWithPassThrough 相同。

@Slf4j
@Component
public class CacheClient {
    public static final Long CACHE_NULL_TTL = 2L;
    public static final String LOCK_SHOP_KEY = "lock:shop:";
    private final RedisTemplate redisTemplate;

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    public CacheClient(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void set(String key, Object value, Long time, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
        // 设置逻辑过期
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        // 写入Redis
        redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    public <R,ID> R queryWithPassThrough(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String json = (String) redisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(json)) {
            // 3.存在,直接返回
            return JSONUtil.toBean(json, type);
        }
        // 判断命中的是否是空值
        if (json != null) {
            // 返回一个错误信息
            return null;
        }

        // 4.不存在,根据id查询数据库
        R r = dbFallback.apply(id);
        // 5.不存在,返回错误
        if (r == null) {
            // 将空值写入redis
            redisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            // 返回错误信息
            return null;
        }
        // 6.存在,写入redis
        this.set(key, r, time, unit);
        return r;
    }

    public <R, ID> R queryWithLogicalExpire(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String json = (String) redisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isBlank(json)) {
            // 3.存在,直接返回
            return null;
        }
        // 4.命中,需要先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        // 5.判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())) {
            // 5.1.未过期,直接返回店铺信息
            return r;
        }
        // 5.2.已过期,需要缓存重建
        // 6.缓存重建
        // 6.1.获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        // 6.2.判断是否获取锁成功
        if (isLock){
            // 6.3.成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    // 查询数据库
                    R newR = dbFallback.apply(id);
                    // 重建缓存
                    this.setWithLogicalExpire(key, newR, time, unit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }finally {
                    // 释放锁
                    unlock(lockKey);
                }
            });
        }
        // 6.4.返回过期的商铺信息
        return r;
    }

    public <R, ID> R queryWithMutex(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String shopJson = (String) redisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            // 3.存在,直接返回
            return JSONUtil.toBean(shopJson, type);
        }
        // 判断命中的是否是空值
        if (shopJson != null) {
            // 返回一个错误信息
            return null;
        }

        // 4.实现缓存重建
        // 4.1.获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        R r = null;
        try {
            boolean isLock = tryLock(lockKey);
            // 4.2.判断是否获取成功
            if (!isLock) {
                // 4.3.获取锁失败,休眠并重试
                Thread.sleep(50);
                return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);
            }
            // 4.4.获取锁成功,根据id查询数据库
            r = dbFallback.apply(id);
            // 5.不存在,返回错误
            if (r == null) {
                // 将空值写入redis
                redisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                // 返回错误信息
                return null;
            }
            // 6.存在,写入redis
            this.set(key, r, time, unit);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            // 7.释放锁
            unlock(lockKey);
        }
        // 8.返回
        return r;
    }

    private boolean tryLock(String key) {
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unlock(String key) {
        redisTemplate.delete(key);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

神经网络|(四)概率论基础知识-古典概型

【1】引言 前序学习了线性回归的基础知识&#xff0c;了解到最小二乘法可以做线性回归分析&#xff0c;但为何最小二乘法如此准确&#xff0c;这需要从概率论的角度给出依据。 因此从本文起&#xff0c;需要花一段时间来回顾概率论的基础知识。 【2】古典概型 古典概型是我…

【C++】特殊类设计、单例模式与类型转换

目录 一、设计一个类不能被拷贝 &#xff08;一&#xff09;C98 &#xff08;二&#xff09;C11 二、设计一个类只能在堆上创建对象 &#xff08;一&#xff09;将构造函数私有化&#xff0c;对外提供接口 &#xff08;二&#xff09;将析构函数私有化 三、设计一个类只…

微服务网关鉴权之sa-token

目录 前言 项目描述 使用技术 项目结构 要点 实现 前期准备 依赖准备 统一依赖版本 模块依赖 配置文件准备 登录准备 网关配置token解析拦截器 网关集成sa-token 配置sa-token接口鉴权 配置satoken权限、角色获取 通用模块配置用户拦截器 api模块配置feign…

16.好数python解法——2024年省赛蓝桥杯真题

问题描述 一个整数如果按从低位到高位的顺序,奇数位(个位、百位、万位…)上的数字是奇数,偶数位(十位、千位、十万位…)上的数字是偶数,我们就称之为“好数”。 给定一个正整数N,请计算从1到N一共有多少个好数。 输入格式 一个整数N。 输出格式 一个整数代表答案。 样例输入 1 …

2025年数学建模美赛 A题分析(2)楼梯使用频率数学模型

2025年数学建模美赛 A题分析&#xff08;1&#xff09;Testing Time: The Constant Wear On Stairs 2025年数学建模美赛 A题分析&#xff08;2&#xff09;楼梯磨损分析模型 2025年数学建模美赛 A题分析&#xff08;3&#xff09;楼梯使用方向偏好模型 2025年数学建模美赛 A题分…

Redis实战(黑马点评)——涉及session、redis存储验证码,双拦截器处理请求

项目整体介绍 数据库表介绍 基于session的短信验证码登录与注册 controller层 // 获取验证码PostMapping("code")public Result sendCode(RequestParam("phone") String phone, HttpSession session) {return userService.sendCode(phone, session);}// 获…

Brightness Controller-源码记录

Brightness Controller 亮度控制 一、概述二、ddcutil 与 xrandr1. ddcutil2. xrandr 三、部分代码解析1. icons2. ui3. utilinit.py 一、概述 项目&#xff1a;https://github.com/SunStorm2018/Brightness.git 原理&#xff1a;Brightness Controlle 是我在 Ubuntu 发现上调…

STM32简介

STM32简介 STM32是ST公司基于ARMCortex-M内核开发的32位微控制器 &#xff08;Microcontroller&#xff09; MCU微控制器、MPU微处理器、CPU中央处理器 1.应用领域 STM32常应用于嵌入式领域。 如智能车&#xff1a;循迹小车 读取光电传感器或者摄像头的数据&#xff0c;…

qt-C++笔记之QLine、QRect、QPainterPath、和自定义QGraphicsPathItem、QGraphicsRectItem的区别

qt-C笔记之QLine、QRect、QPainterPath、和自定义QGraphicsPathItem、QGraphicsRectItem的区别 code review! 参考笔记 1.qt-C笔记之重写QGraphicsItem的paint方法(自定义QGraphicsItem) 文章目录 qt-C笔记之QLine、QRect、QPainterPath、和自定义QGraphicsPathItem、QGraphic…

浏览器IndexedDB占用大

使用鲁大师清理后&#xff0c;用 SpaceSniffer 查看C盘占用情况&#xff0c;发现浏览器的 IndexedDB 有3个文件夹占用特别大&#xff0c;从文件名看是 youku&#xff0c;bilibili&#xff0c;v.qq.com&#xff0c;浏览器的数据库并不需要长期保存&#xff0c;删除这3个文件夹&a…

MongoDB部署模式

目录 单节点模式&#xff08;Standalone&#xff09; 副本集模式&#xff08;Replica Set&#xff09; 分片集群模式&#xff08;Sharded Cluster&#xff09; MongoDB有多种部署模式&#xff0c;可以根据业务需求选择适合的架构和部署方式。 单节点模式&#xff08;Standa…

将 OneLake 数据索引到 Elasticsearch - 第二部分

作者&#xff1a;来自 Elastic Gustavo Llermaly 及 Jeffrey Rengifo 本文分为两部分&#xff0c;第二部分介绍如何使用自定义连接器将 OneLake 数据索引并搜索到 Elastic 中。 在本文中&#xff0c;我们将利用第 1 部分中学到的知识来创建 OneLake 自定义 Elasticsearch 连接器…

Formality:时序变换(三)(相位反转)

相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 一、引言 时序变换在Design Compiler的首次综合和增量综合中都可能发生&#xff0c;它们包括&#xff1a;时钟门控(Clock Gating)、寄存器合并(Register Merging)、…

php代码审计2 piwigo CMS in_array()函数漏洞

php代码审计2 piwigo CMS in_array()函数漏洞 一、目的 本次学习目的是了解in_array()函数和对项目piwigo中关于in_array()函数存在漏洞的一个审计并利用漏洞获得管理员帐号。 二、in_array函数学习 in_array() 函数搜索数组中是否存在指定的值。 in_array($search,$array…

房租管理系统的智能化应用助推租赁行业高效运营与决策优化

内容概要 在现代租赁行业中&#xff0c;房租管理系统的智能化应用正在逐步成为一个不可或缺的工具。通过整合最新技术&#xff0c;这些系统为租赁管理的各个方面提供了极大的便利和效率提升。从房源管理到合同签署再到财务监控&#xff0c;智能化功能能够帮助运营者在繁琐的事…

Hive关于数据库的语法,warehouse,metastore

关于数据库的语法 在default数据库下,查看其他数据库的表 in 打开控制台 字体大小的设置 Hive默认的库: default, 1/4说明一共有4个库,现在只展示了1个,单击>>所有架构 数据库的删除 方法一: 语法 删除有表的数据库,加cascade 方法二 当前连接的数据库 切换当前数据库…

【React】PureComponent 和 Component 的区别

前言 在 React 中&#xff0c;PureComponent 和 Component 都是用于创建组件的基类&#xff0c;但它们有一个主要的区别&#xff1a;PureComponent 会给类组件默认加一个shouldComponentUpdate周期函数。在此周期函数中&#xff0c;它对props 和 state (新老的属性/状态)会做一…

AI赋能医疗:智慧医疗系统源码与互联网医院APP的核心技术剖析

本篇文章&#xff0c;笔者将深入剖析智慧医疗系统的源码架构以及互联网医院APP背后的核心技术&#xff0c;探讨其在医疗行业中的应用价值。 一、智慧医疗系统的核心架构 智慧医疗系统是一个高度集成的信息化平台&#xff0c;主要涵盖数据采集、智能分析、决策支持、远程医疗等…

HTML-新浪新闻-实现标题-样式1

用css进行样式控制 css引入方式&#xff1a; --行内样式&#xff1a;写在标签的style属性中&#xff08;不推荐&#xff09; --内嵌样式&#xff1a;写在style标签中&#xff08;可以写在页面任何位置&#xff0c;但通常约定写在head标签中&#xff09; --外联样式&#xf…

【学习笔记】深度学习网络-深度前馈网络(MLP)

作者选择了由 Ian Goodfellow、Yoshua Bengio 和 Aaron Courville 三位大佬撰写的《Deep Learning》(人工智能领域的经典教程&#xff0c;深度学习领域研究生必读教材),开始深度学习领域学习&#xff0c;深入全面的理解深度学习的理论知识。 在之前的文章中介绍了深度学习中用…