Redis——某马点评day02——商铺缓存

news2025/2/25 7:59:16

什么是缓存

添加Redis缓存

添加商铺缓存

Controller层中

    /**
     * 根据id查询商铺信息
     * @param id 商铺id
     * @return 商铺详情数据
     */
    @GetMapping("/{id}")
    public Result queryShopById(@PathVariable("id") Long id) {
        return shopService.queryById(id);
    }

Service层中

 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryById(Long id) {
        String key="cache:shop:" + id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //4.不存在,根据id查询数据库
        Shop shop = getById(id);
        if (shop==null) {
            //5.不存在,返回错误
            return Result.fail("店铺不存在");
        }
        //6.存在,写入Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop));
        //7.返回
        return Result.ok(shop);
    }
}

练习添加店铺类型缓存

Controller层中

@RestController
@RequestMapping("/shop-type")
public class ShopTypeController {
    @Resource
    private IShopTypeService typeService;

    @GetMapping("list")
    public Result queryTypeList() {
       return typeService.queryTypeList();
    }
}

Service层中

    @Override
    public Result queryTypeList() {
        String key="cache:shopType";
        //1.从Redis查询缓存
        String shopType = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopType)) {
            //3.存在,直接返回
            List<ShopType> typeList = JSONUtil.toList(shopType, ShopType.class);
            return Result.ok(typeList);
        }
        //4.不存在,查询数据库
        List<ShopType> typeList = query().orderByAsc("sort").list();
        //5.存在,写入Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(typeList));
        //7.返回
        return Result.ok(typeList);
    }

缓存更新策略

 通常选择的方案都是第一种

单体系统可以通过@Transactional注解完成事务。

通常是先操作数据库,再删除缓存,出现问题的几率极小。

 

 实现商铺缓存和数据库的双写一致

第一个地方,写入Redis时加上超时时间。 

  //6.存在,写入Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);

 第二个地方

controller中

    /**
     * 更新商铺信息
     * @param shop 商铺数据
     * @return 无
     */
    @PutMapping
    public Result updateShop(@RequestBody Shop shop) {
        return shopService.update(shop);
    }

service中

    @Override
    public Result update(Shop shop) {
        Long id = shop.getId();
        if(id==null){
            return Result.fail("店铺id不能为空");
        }
        //1.更新数据库
        updateById(shop);
        //2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY+id);
        return null;
    }

缓存穿透

布隆过滤器的实现不是真的存储数据,而是用某种Hash算法计算之后用二进制压缩之类的方法保存是否存在。但是,也有可能多个数据hash值相同导致错误结果。

编码解决商铺查询的缓存穿透(缓存空对象做法)

 代码修改

    @Override
    public Result queryById(Long id) {
        String key=CACHE_SHOP_KEY+ id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //判断命中的是否是空值
        if(shopJson!=null){
            //返回一个错误信息
            return Result.fail("店铺不存在");
        }
        //4.不存在,根据id查询数据库
        Shop shop = getById(id);
        if (shop==null) {
            //将空值写入Redis
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
            //5.不存在,返回错误
            return Result.fail("店铺不存在");
        }
        //6.存在,写入Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //7.返回
        return Result.ok(shop);
    }

 限流可以用sentinel实现.

缓存雪崩

宕机时降级限流也是用sentinel实现。

nginx缓存也是一级缓存.

tmd,一直在说springcloud里面有讲。

缓存击穿

常见解决方案

这里可以参考一下redisson的源码设计思路,设计一个监听通知机制! 

逻辑过期解决方案不会设置ttl过期时间,而是新增一个exprie字段,从redis里面查询发现是过期数据时就需要加锁开启一个新线程去更新缓存,然后直接返回旧数据。有别的线程来获取锁失败时说明已经有线程在进行更新,所以就直接返回过期数据,避免了过多线程等待锁。

 

利用互斥锁解决缓存击穿问题(重点)

这里的锁不能用lock和synchronized进行互斥实现,这两个会一直等待.这里用到Redis的一个命令setnx, 这个是一旦设置之后就不能修改,只能删除,但是如果因为意外原因导致迟迟不能删除会有大问题,所以这里会给锁设置一个有效期.

 代码修改

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryById(Long id) {
        //缓存穿透
        //Shop shop=queryWithPassThrouh(id);
        //互斥锁解决缓存击穿
        Shop shop = queryWithMutex(id);
        if(shop==null){
            return Result.fail("店铺不存在!");
        }
        //7.返回
        return Result.ok(shop);
    }

    public Shop queryWithMutex(Long id){
        String key=CACHE_SHOP_KEY+ id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        //判断命中的是否是空值
        if(shopJson!=null){
            //返回一个错误信息
            return null;
        }
        //4.实现缓存重建
        //4.1获取互斥锁
        String lockkey="lock:shop:"+id;
        Shop shop = null;
        try {
            boolean isLock = tryLock(lockkey);
            //4.2判断是否获取成功
            if(!isLock){
                //4.3失败,休眠并重试
                Thread.sleep(50);
              return  queryWithMutex(id);   //这里有可能会出现栈溢出的情况。
            }
            //获取成功之后应该再次检查缓存是否存在,有可能别的线程已经重建完了缓存,所以这里就无需再重建缓存
            shopJson = stringRedisTemplate.opsForValue().get(key);
            //再次判断是否存在
            if (StrUtil.isNotBlank(shopJson)) {
                //存在,直接返回
                return JSONUtil.toBean(shopJson, Shop.class);
            }
            //4.4根据id查询数据库
            shop = getById(id);
            //模拟重建的延时
            Thread.sleep(200);
            if (shop==null) {
                //将空值写入Redis
                stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
                //5.不存在,返回错误
                return shop;
            }
            //6.存在,写入Redis
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //7.释放互斥锁
            unlock(lockkey);
        }
        //8.返回
        return shop;
    }

    public Shop queryWithPassThrouh(Long id){
        String key=CACHE_SHOP_KEY+ id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return shop;
        }
        //判断命中的是否是空值
        if(shopJson!=null){
            //返回一个错误信息
            return null;
        }
        //4.不存在,根据id查询数据库
        Shop shop = getById(id);
        if (shop==null) {
            //将空值写入Redis
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
            //5.不存在,返回错误
            return shop;
        }
        //6.存在,写入Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //7.返回
        return shop;
    }

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

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


    @Override
    public Result update(Shop shop) {
        Long id = shop.getId();
        if(id==null){
            return Result.fail("店铺id不能为空");
        }
        //1.更新数据库
        updateById(shop);
        //2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY+id);
        return null;
    }
}

这里可以上Jmeter进行压测,上100个线程进行测试

但是最终实际只查询了一次数据库. 

利用逻辑过期解决缓存击穿问题(重点)

 为了能增加一个逻辑过期时间的字段,新建一个对象

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

代码修改

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryById(Long id) {
        //缓存穿透
        //Shop shop=queryWithPassThrouh(id);
        //互斥锁解决缓存击穿
//        Shop shop = queryWithMutex(id);

        //逻辑过期解决缓存击穿问题
        Shop shop = queryWithLogicalExpire(id);
        if(shop==null){
            return Result.fail("店铺不存在!");
        }
        //7.返回
        return Result.ok(shop);
    }


    private static final ExecutorService CACHE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10);
    public Shop queryWithLogicalExpire(Long id){
        String key=CACHE_SHOP_KEY+ id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isBlank(shopJson)) {
            //3.存在,直接返回null
            return null;
        }
        //4.命中,需要先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        Shop shop = JSONUtil.toBean((JSONObject)redisData.getData(), Shop.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        //5.判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())){
            //5.1未过期,直接返回店铺信息
            return shop;
        }
        //5.2已过期,需要缓存重建
        //6.缓存重建
        //6.1获取互斥锁
        String lockKey=LOCK_SHOP_KEY+id;
        boolean isLock = tryLock(lockKey);
        //6.2判断是否获取锁成功
        if(isLock){
            //这里应该再次检测缓存是否过期,做双重判断,如果没过期就不需重建了,因为可能别的线程已经重建了
             shopJson = stringRedisTemplate.opsForValue().get(key);
             redisData = JSONUtil.toBean(shopJson, RedisData.class);
            expireTime = redisData.getExpireTime();
            if(expireTime.isAfter(LocalDateTime.now())){
                //返回前先释放锁
                unlock(lockKey);
                //5.1未过期,直接返回店铺信息
                return shop;
            }
            //6.3成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    //重建缓存
                    this.saveShop2Redis(id,20L);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //释放锁
                    unlock(lockKey);
                }
            });
        }
        //6.4失败,返回过期商铺信息。
        return shop;
    }

    public Shop queryWithMutex(Long id){
        String key=CACHE_SHOP_KEY+ id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);

        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        //判断命中的是否是空值
        if(shopJson!=null){
            //返回一个错误信息
            return null;
        }
        //4.实现缓存重建
        //4.1获取互斥锁
        String lockkey="lock:shop:"+id;
        Shop shop = null;
        try {
            boolean isLock = tryLock(lockkey);
            //4.2判断是否获取成功
            if(!isLock){
                //4.3失败,休眠并重试
                Thread.sleep(50);
              return  queryWithMutex(id);   //这里有可能会出现栈溢出的情况。
            }
            //获取成功之后应该再次检查缓存是否存在,有可能别的线程已经重建完了缓存,所以这里就无需再重建缓存
            shopJson = stringRedisTemplate.opsForValue().get(key);
            //再次判断是否存在
            if (StrUtil.isNotBlank(shopJson)) {
                //存在,直接返回
                return JSONUtil.toBean(shopJson, Shop.class);
            }
            //4.4根据id查询数据库
            shop = getById(id);
            //模拟重建的延时
            //Thread.sleep(200);
            if (shop==null) {
                //将空值写入Redis
                stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
                //5.不存在,返回错误
                return shop;
            }
            //6.存在,写入Redis
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //7.释放互斥锁
            unlock(lockkey);
        }
        //8.返回
        return shop;
    }

    public Shop queryWithPassThrouh(Long id){
        String key=CACHE_SHOP_KEY+ id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return shop;
        }
        //判断命中的是否是空值
        if(shopJson!=null){
            //返回一个错误信息
            return null;
        }
        //4.不存在,根据id查询数据库
        Shop shop = getById(id);
        if (shop==null) {
            //将空值写入Redis
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
            //5.不存在,返回错误
            return shop;
        }
        //6.存在,写入Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //7.返回
        return shop;
    }

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

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

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

    @Override
    public Result update(Shop shop) {
        Long id = shop.getId();
        if(id==null){
            return Result.fail("店铺id不能为空");
        }
        //1.更新数据库
        updateById(shop);
        //2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY+id);
        return null;
    }
}

缓存工具封装(重点)

封装工具类里用到的实体

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

工具类代码

@Slf4j
@Component
public class CacheClient {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    String LOCK_SHOP_KEY="lock:shop:";
    
    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
    public  void set(String key, Object value, Long time, TimeUnit timeUnit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,timeUnit);
    }

    public  void setWithLogicalExpire(String key, Object value, Long time, TimeUnit timeUnit){
        //设置逻辑过期
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)));
        //写入Redis
        stringRedisTemplate.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 timeUnit){
        String key=keyPrefix+id;
        //1.从Redis查询缓存
        String json = stringRedisTemplate.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
            stringRedisTemplate.opsForValue().set(key,"",2L, TimeUnit.MINUTES);
            return null;
        }
        //6.存在,写入Redis
        this.set(key,r,time,timeUnit);
        //7.返回
        return r;
    }

    private static final ExecutorService CACHE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10);
    public <R,ID> R queryWithLogicalExpire(
            String keyPrefix, ID id, Class<R> type, Function<ID,R>dbFallback, Long time, TimeUnit timeUnit){
        String key=keyPrefix+ id;
        //1.从Redis查询缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isBlank(json)) {
            //3.存在,直接返回null
            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){
            //这里应该再次检测缓存是否过期,做双重判断,如果没过期就不需重建了,因为可能别的线程已经重建了
            json = stringRedisTemplate.opsForValue().get(key);
            redisData = JSONUtil.toBean(json, RedisData.class);
            r = JSONUtil.toBean((JSONObject)redisData.getData(), type);
            expireTime = redisData.getExpireTime();
            if(expireTime.isAfter(LocalDateTime.now())){
                //返回前先释放锁
                unlock(lockKey);
                //5.1未过期,直接返回店铺信息
                return r;
            }
            //6.3成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    //重建缓存
                    //查询数据库
                    R r1 = dbFallback.apply(id);
                    //写入Redis
                    this.setWithLogicalExpire(key,r1,time,timeUnit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //释放锁
                    unlock(lockKey);
                }
            });
        }
        //6.4失败,返回过期信息。
        return r;
    }

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

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

Service层修改后代码

里面有缓存穿透的调用,也有缓存击穿的调用.

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private CacheClient cacheClient;
    @Override
    public Result queryById(Long id) {
        //缓存穿透
        Shop shop=cacheClient.queryWithPassThrough(CACHE_SHOP_KEY,id, Shop.class,id2->getById(id2),CACHE_SHOP_TTL,TimeUnit.MINUTES);
        //Shop shop=cacheClient.queryWithPassThrough(CACHE_SHOP_KEY,id, Shop.class,this::getById,,CACHE_SHOP_TTL,TimeUnit.MINUTES);
        //互斥锁解决缓存击穿
        //Shop shop = queryWithMutex(id);
        //逻辑过期解决缓存击穿问题
//        Shop shop = cacheClient
//                .queryWithLogicalExpire(CACHE_SHOP_KEY,id,Shop.class,this::getById,CACHE_SHOP_TTL,TimeUnit.SECONDS);
        if(shop==null){
            return Result.fail("店铺不存在!");
        }
        //7.返回
        return Result.ok(shop);
    }


    @Override
    public Result update(Shop shop) {
        Long id = shop.getId();
        if(id==null){
            return Result.fail("店铺id不能为空");
        }
        //1.更新数据库
        updateById(shop);
        //2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY+id);
        return null;
    }
}

内容总结:

去看文档资料里面xmind文档,那个里面总结的很好。

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

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

相关文章

XIAO ESP32S3之SenseCraft 模型助手部署

sipeed教程&#xff1a;SenseCraft 模型助手部署 | Seeed Studio Wiki 一、安装ESP-IDF 鉴于我的电脑之前安装过esp-idf v4.3版本&#xff0c;而ESP32-S3需要v4.4及以上版本才支持&#xff0c;所以将esp-idf更新到最新5.1版本。 1、启动mingw32.exe应用 2、进入esp-idf目录 …

docker+jmeter+influxdb+granfana

centos7国内阿里源安装docker 1、安装必要的系统工具 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 2添加官方仓库 sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.reposudo sed -i sdownload.doc…

Opencv拖动条控制均值滤波卷积核大小,拖动条控制是否保存(涉及知识点:cv2.createTrackbar和cv2.getTrackbarPos的使用)

带拖动条的均值滤波import timeimport cv2 import numpy as npdef callback(int):passcv2.namedWindow(dst,cv2.WINDOW_AUTOSIZE)# 创建trackbar (trackbarname,winname,value,count,callback,userdata) cv2.createTrackbar(ksize, dst, 3, 30, callback) cv2.createTrackbar(s…

QT 中 QTimer 类 备查

基础 // 指定了父对象, 创建的堆内存可以自动析构 QTimer::QTimer(QObject *parent nullptr);// 根据指定的时间间隔启动或者重启定时器, 需要调用 setInterval() 设置时间间隔 void QTimer::start();// 启动或重新启动定时器&#xff0c;超时间隔为msec毫秒。 void QTimer::…

【NeurIPS 2023】PromptIR: Prompting for All-in-One Blind Image Restoration

PromptIR: Prompting for All-in-One Blind Image Restoration&#xff0c; NeurIPS 2023 论文&#xff1a;https://arxiv.org/abs/2306.13090 代码&#xff1a;https://github.com/va1shn9v/promptir 解读&#xff1a;即插即用系列 | PromptIR&#xff1a;MBZUAI提出一种基…

node.js express路由和中间件

目录 路由 解释 使用方式 中间件 解释 使用方式 中间件类型 路由注册和中间件注册 代码 app全局路由接口请求以及代码解析 示例1 示例2 示例3 示例4 中间件req继承 嵌套子路由 解释 代码 示例1 路由 解释 在 Express 中&#xff0c;路由&#xff08;Route&…

Spring | Spring的基本应用

目录: 1.什么是Spring&#xff1f;2.Spring框架的优点3.Spring的体系结构 (重点★★★) :3.1 Core Container (核心容器) ★★★Beans模块 (★★★) : BeanFactoryCore核心模块 (★★★) : IOCContext上下文模块 (★★★) : ApplicationContextContext-support模块 (★★★)SpE…

Linux中文件的打包压缩、解压,下载到本地——zip,tar指令等

目录 1 .zip后缀名&#xff1a; 1.1 zip指令 1.2 unzip指令 2 .tar后缀名 3. sz 指令 4. rz 指令 5. scp指令 1 .zip后缀名&#xff1a; 1.1 zip指令 语法&#xff1a;zip [namefile.zip] [namefile]... 功能&#xff1a;将目录或者文件压缩成zip格式 常用选项&#xff1a…

《YOLOv8原创自研》专栏介绍 CSDN独家改进创新实战专栏目录

YOLOv8原创自研 https://blog.csdn.net/m0_63774211/category_12511737.html?spm1001.2014.3001.5482 &#x1f4a1;&#x1f4a1;&#x1f4a1;全网独家首发创新&#xff08;原创&#xff09;&#xff0c;适合paper &#xff01;&#xff01;&#xff01; &#x1f4a1;&a…

QT 中 QProgressDialog 进度条窗口 备查

基础API //两个构造函数 QProgressDialog::QProgressDialog(QWidget *parent nullptr, Qt::WindowFlags f Qt::WindowFlags());QProgressDialog::QProgressDialog(const QString &labelText, const QString &cancelButtonText, int minimum, int maximum, QWidget *…

java源码-类与对象

1、类与对象的初步认知 在了解类和对象之前我们先了解一下什么是面向过程和面向对象。 1&#xff09;面向过程编程&#xff1a; C语言就是面向过程编程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 2&#xff09;面向对…

【数据结构】最短路径(Dijskra算法)

一.引例 计算机网络传输的问题&#xff1a; 怎样找到一种最经济的方式&#xff0c;从一台计算机向网上所有其他计算机发送一条消息。 抽象为&#xff1a; 给定带权有向图G&#xff08;V&#xff0c;E&#xff09;和源点v&#xff0c;求从v到G中其余各顶点的最短路径。 即&…

23.Python 图形化界面编程

目录 1.认识GUI和使用tkinter2.使用组件2.1 标签2.2 按钮2.3 文本框2.4 单选按钮和复选按钮2.5 菜单和消息2.6 列表框2.7 滚动条2.8 框架2.9 画布 3. 组件布局4.事件处理 1.认识GUI和使用tkinter 人机交互是从人努力适应计算机&#xff0c;到计算机不断适应人的发展过程&#…

Redis中的数据结构

文章目录 第1关&#xff1a;Redis中的数据结构 第1关&#xff1a;Redis中的数据结构 这是上篇文章的第一关&#xff0c;只不过本篇是代码按行做的&#xff0c;方便一下大家使用。 代码如下&#xff1a; redis-cliset hello redislpush educoder-list hellorpush educoder-lis…

cmake和vscode 下的cmake的使用详解(三)

第七讲&#xff1a;【实战】使用 VSCode 进行完整项目开发 案例&#xff1a;士兵突击 需求&#xff1a; 1. 士兵 许三多 有一把 枪 &#xff0c;叫做 AK47 2. 士兵 可以 开火 3. 士兵 可以 给枪装填子弹 4. 枪 能够 发射 子弹 5. 枪 能够 装填子弹 ——…

初识RocketMQ

1、简介 RocketMQ 是阿里巴巴在 2012 年开源的消息队列产品&#xff0c;用 Java 语言实现&#xff0c;在设计时参考了 Kafka&#xff0c;并做出了自己的一些改进&#xff0c;后来捐赠给 Apache 软件基金会&#xff0c;2017 正式毕业&#xff0c;成为 Apache 的顶级项目。Rocket…

canvas基础:绘制贝塞尔曲线

canvas实例应用100 专栏提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。 canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重要的帮助。 文章目录 bez…

pygame实现贪吃蛇小游戏

import pygame import random# 游戏初始化 pygame.init()# 游戏窗口设置 win_width, win_height 800, 600 window pygame.display.set_mode((win_width, win_height)) pygame.display.set_caption("Snake Game")# 颜色设置 WHITE (255, 255, 255) BLACK (0, 0, 0…

docker容器内部文件挂载主机

docker images执行该命令可以发现一个centos镜像 docker run --namemycentos -itd --privilegedtrue --restartalways -p 88:80 -v C:\Users\Administrator\Desktop\dockerTest:/bin/gh:ro centosdocker run 命令用于在 Docker 上创建和运行容器。 --namemycentos 指定容器…

SHAP(四):NHANES I 生存模型

SHAP&#xff08;四&#xff09;&#xff1a;NHANES I 生存模型 这是一个 Cox 比例风险模型&#xff0c;基于来自 NHANES I 的数据以及来自 NHANES I 流行病学随访研究。 它旨在说明 SHAP 值如何能够以传统上仅由线性模型提供的清晰度解释 XGBoost 模型。 我们在数据中看到有趣…