手写redis实现分布式锁详细教程,满足可续锁、可重入等分布式锁条件

前言

本文将讨论的做一个高并发场景下避不开的话题,即redis分布式锁。比如在淘宝 的秒杀场景、热点新闻和热搜排行榜等。可见分布式锁是一个程序员面向高级的一门必修课,下面请跟着本篇文章好好学习。

redis分布式锁有哪些面试题

1.Redis做分布式的时候需要注意什么问题?
2.你们公司自己实现的分布式锁是否用的setnx命令实现?这个是最合适的吗?你如何考虑分布式锁的可重入问题?
3.如果Redis是单点部署的,会带来什么问题?准备怎么解决单点问题呢?
Redis集群模式下,比如主从模式下,CAP方面有没有什么问题?

1.分布式锁是什么?

1.1 锁的种类介绍

锁的种类锁的概念
单机单机版同一个JVM虚拟机内,synchronized或者lock接口
分布式分布式多个不同的java虚拟机,单机的线程锁机制不再起作用了,资源类在不同的服务器之间共享了。

1.2 一个正经的分布式锁具有哪些刚需

在这里插入图片描述
独占性:任何时刻只能有且仅有一个线程持有
高可用:若redis集群环境下,不能因为某个节点挂了而出现获取锁或者释放锁失败。高并发请求下依旧能够保证良好使用。
防止死锁:杜绝死锁,必须有超时控制或者撤销操作,有个兜底终止跳出方案
不乱抢:防止张冠李戴,不能私下uolock别人的锁,只能自己加锁自己释放,自己约的锁自己要释放,可以设置过期时间,或者业务代码执行完毕以后删除对一个的锁。
可重入:同一个节点的同一个线程如果获得锁之后,他也可以再次获得这个锁。

1.3 redis分布式锁

setnx key values

1.4 java实现分布式锁的案例

先来个乞丐版的分布式锁,并没有遵循上面五大原则。然后慢慢进行优化,乞丐版分布锁案例如下代码所示:

public String sale() {String resMessgae = "";String key = "luojiaRedisLocak";String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);// 抢不到的线程继续重试if (!flag) {// 线程休眠20毫秒,进行递归重试try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}sale();} else {try {// 1 抢锁成功,查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory01");// 2 判断库存书否足够Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);// 3 扣减库存,每次减少一个库存if (inventoryNum > 0) {stringRedisTemplate.opsForValue().set("inventory01", String.valueOf(--inventoryNum));resMessgae = "成功卖出一个商品,库存剩余:" + inventoryNum + "\t" + ",服务端口号:" + port;log.info(resMessgae);} else {resMessgae = "商品已售罄。" + "\t" + ",服务端口号:" + port;log.info(resMessgae);}} finally {stringRedisTemplate.delete(key);}}return resMessgae;
}

请看看以上代码有哪些问题?既没有删除过期时间 ,也没有判断redis获取的redis值进行删除,有可能删除错锁。如果进一步优化可以redis可以存一个流水号,业务代码执行完了以后,判断流水号是否相等,然后进行删除。可重入问题可以通过递归实现重试,但是依旧有问题:手工设置5000个线程来抢占锁,压测OK,但是容易导致StackOverflowError,在高并发不推荐使用,需要进一步完善。改进获取重试方法代码如下所示:

public String sale() {String resMessgae = "";String key = "luojiaRedisLocak";// 标记线程id,知道使哪个线程在执行String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();// 不用递归了,高并发容易出错,我们用自旋代替递归方法重试调用;也不用if,用while代替while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue)) {// 线程休眠20毫秒,进行递归重试try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}}try {// 1 抢锁成功,查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory01");// 2 判断库存书否足够Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);// 3 扣减库存,每次减少一个库存if (inventoryNum > 0) {stringRedisTemplate.opsForValue().set("inventory01", String.valueOf(--inventoryNum));resMessgae = "成功卖出一个商品,库存剩余:" + inventoryNum + "\t" + ",服务端口号:" + port;log.info(resMessgae);} else {resMessgae = "商品已售罄。" + "\t" + ",服务端口号:" + port;log.info(resMessgae);}} finally {stringRedisTemplate.delete(key);}return resMessgae;
}

为了防止出现死锁,需要给锁设置过期时,关键点在于过期时间设置,以避免代码异常出现,而该线程持续占有该锁。其java代码如下所示:

while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {// 线程休眠20毫秒,进行递归重试try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}}

为了防止误删key,在执行完了业务代码以后需要删掉锁,在try-catch-finally 中添加如下删除锁的代码 :

 try {//和上一个代码块重复,省略掉了} finally {// v5.0 改进点,判断加锁与解锁是不同客户端,自己只能删除自己的锁,不误删别人的锁if (stringRedisTemplate.opsForValue().get(key).equalsIgnoreCase(uuidValue)) {stringRedisTemplate.delete(key);}}

在finally中删除key并不能保持它的原子性,当业务执行时间大于锁的过期时间是,其他线程可以抢占该锁,没法保证业务执行的完整性,所以以下代码借助Lua脚本进行优化,java代码如下所示:

public String sale() {String resMessgae = "";String key = "luojiaRedisLocak";String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();// 不用递归了,高并发容易出错,我们用自旋代替递归方法重试调用;也不用if,用while代替while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {// 线程休眠20毫秒,进行递归重试try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}}try {// 1 抢锁成功,查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory01");// 2 判断库存书否足够Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);// 3 扣减库存,每次减少一个库存if (inventoryNum > 0) {stringRedisTemplate.opsForValue().set("inventory01", String.valueOf(--inventoryNum));resMessgae = "成功卖出一个商品,库存剩余:" + inventoryNum + "\t" + ",服务端口号:" + port;log.info(resMessgae);} else {resMessgae = "商品已售罄。" + "\t" + ",服务端口号:" + port;log.info(resMessgae);}} finally {// 改进点,修改为Lua脚本的Redis分布式锁调用,必须保证原子性,参考官网脚本案例String luaScript ="if redis.call('get',KEYS[1]) == ARGV[1] then " +"return redis.call('del',KEYS[1]) " +"else " +"return 0 " +"end";stringRedisTemplate.execute(new DefaultRedisScript(luaScript, Boolean.class), Arrays.asList(key), uuidValue);}return resMessgae;
}

上述代码,既然所已经被删除了,如何兼顾锁的可重入问题?这个问题下文会做出解释,希望读者耐心看完。可以在业务代码(同步代码块)前后添加lock和unlock实现加锁。而今重新进入时没必要重新获得一把锁。
可重入锁的概念:
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有synchronized修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可以一定程度避免死锁。下文1.5将展示详细的可重入分布式锁。

1.5 优化分布式锁

本次优化主要解决的问题有:宕机防止死锁、防止误删key、Lua保证原子性。设置 过期时间的同时,当业务执行时间大于过期时间,自动续锁功能等,分布式锁实现可重入需要使用的hset,记录进入次数。java代码如下所示:

新增续锁功能,java代码如下所示,其中实现步骤为:
步骤一:复原程序为初识无锁版本(即上述乞丐版)
步骤二:新建RedisDistributedLock类实现JUC里面的Lock接口
步骤三:满足JUC里面AQS对Lock锁的接口规范定义来进行实现落地代码
步骤四:结合设计模式开发属于自己的Redis分布式锁工具类
```java
package com.luojia.redislock.mylock;import cn.hutool.core.util.IdUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;/*** 自研的分布式锁,实现了Lock接口*/
public class RedisDistributedLock implements Lock {private StringRedisTemplate stringRedisTemplate;private String lockName; // KEYS[1]private String uuidValule; // ARGV[1]private long expireTime; // ARGV[2]public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName) {this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;this.uuidValule = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();this.expireTime = 50L;}@Overridepublic void lock() {tryLock();}@Overridepublic boolean tryLock() {try {tryLock(-1L, TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}return false;}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {if (-1 == time) {//lua脚本加锁String script ="if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " +"redis.call('hincrby', KEYS[1], ARGV[1], 1) " +"redis.call('expire', KEYS[1], ARGV[2]) " +"return 1 " +"else " +"return 0 " +"end";System.out.println("lockName:" + lockName + "\t" + "uuidValue:" + uuidValule);// 加锁失败需要自旋一直获取锁while (!stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),Arrays.asList(lockName),uuidValule,String.valueOf(expireTime))) {// 休眠60毫秒再来重试try {TimeUnit.MILLISECONDS.sleep(60);} catch (InterruptedException e) {e.printStackTrace();}}return true;}return false;}@Overridepublic void unlock() {//lua脚本解锁String script = "" +"if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then " +"return nil " +"elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 then " +"return redis.call('del', KEYS[1]) " +"else " +"return 0 " +"end";System.out.println("lockName:" + lockName + "\t" + "uuidValue:" + uuidValule);// LUA脚本由C语言编写,nil -> false; 0 -> false; 1 -> true;// 所以此处DefaultRedisScript构造函数返回值不能是Boolean,Boolean没有nilLong flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class),Arrays.asList(lockName),uuidValule);if (null == flag) {throw new RuntimeException("this lock does not exists.");}}// 下面两个暂时用不到,不用重写@Overridepublic void lockInterruptibly() throws InterruptedException {}@Overridepublic Condition newCondition() {return null;}
}

其中lua脚本加锁说明,如下图所示:
在这里插入图片描述
返回为1情况说明。
在这里插入图片描述
lua脚本解锁说明
在这里插入图片描述
完整的分布式锁java代码如下所示:

// v7.0 使用自研的lock/unlock+LUA脚本自研的Redis分布式锁
Lock redisDistributedLock = new RedisDistributedLock(stringRedisTemplate, "luojiaRedisLock");
public String sale() {String resMessgae = "";redisDistributedLock.lock();try {// 1 抢锁成功,查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory01");// 2 判断库存书否足够Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);// 3 扣减库存,每次减少一个库存if (inventoryNum > 0) {stringRedisTemplate.opsForValue().set("inventory01", String.valueOf(--inventoryNum));resMessgae = "成功卖出一个商品,库存剩余:" + inventoryNum + "\t" + ",服务端口号:" + port;log.info(resMessgae);} else {resMessgae = "商品已售罄。" + "\t" + ",服务端口号:" + port;log.info(resMessgae);}} finally {redisDistributedLock.unlock();}return resMessgae;
}

小总结
在这里插入图片描述
引入工厂模式
可重入测试,在InventoryService类新增可重入测试方法。

// v7.1 使用工厂类创建锁
@Autowired
private DistributedLockFactory distributedLockFactory;
public String sale() {String resMessgae = "";Lock redisLock = distributedLockFactory.getDistributedLock("REDIS", "luojiaRedisLock");redisLock.lock();try {// 1 抢锁成功,查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory01");// 2 判断库存书否足够Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);// 3 扣减库存,每次减少一个库存if (inventoryNum > 0) {stringRedisTemplate.opsForValue().set("inventory01", String.valueOf(--inventoryNum));resMessgae = "成功卖出一个商品,库存剩余:" + inventoryNum + "\t" + ",服务端口号:" + port;log.info(resMessgae);testReEntry();} else {resMessgae = "商品已售罄。" + "\t" + ",服务端口号:" + port;log.info(resMessgae);}} finally {redisLock.unlock();}return resMessgae;
}private void testReEntry() {Lock redisLock = distributedLockFactory.getDistributedLock("REDIS", "luojiaRedisLock");redisLock.lock();try {log.info("=================测试可重入锁=================");} finally {redisLock.unlock();}
}

引入工厂模式

package com.luojia.redislock.mylock;import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.concurrent.locks.Lock;@Component
public class DistributedLockFactory {@Autowiredprivate StringRedisTemplate stringRedisTemplate;private String uuid;public DistributedLockFactory() {this.uuid = IdUtil.simpleUUID();}public Lock getDistributedLock(String lockType, String lockName) {if (lockType == null) {return null;}if ("REDIS".equalsIgnoreCase(lockType)) {return new RedisDistributedLock(stringRedisTemplate, lockName, uuid);} else if ("ZOOKEEPER".equalsIgnoreCase(lockType)) {// 后面存在就返回对应的分布式锁} else if ("MYSQL".equalsIgnoreCase(lockType)) {// 后面存在就返回对应的分布式锁}return null;}
}

在RedisDistributedLock中,修改构造方法:

public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuid) {this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;this.uuidValule = uuid + ":" + Thread.currentThread().getId();this.expireTime = 50L;
}

锁的自动续费功能
确保RedisLock过期时间大于业务执行时间的问题,以便确保时间到了,业务没有执行完需要自动续期,对tryLock进行改正。
自动续锁的Lua脚本:

// 自动续期的LUA脚本
if redis.call('hexists', KEYS[1], ARGV[1]) == 1 thenreturn redis.call('expire', KEYS[1], ARGV[2])
elsereturn 0
end
```java
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {if (-1 == time) {String script ="if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " +"redis.call('hincrby', KEYS[1], ARGV[1], 1) " +"redis.call('expire', KEYS[1], ARGV[2]) " +"return 1 " +"else " +"return 0 " +"end";System.out.println("lock() lockName:" + lockName + "\t" + "uuidValue:" + uuidValule);// 加锁失败需要自旋一直获取锁while (!stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),Arrays.asList(lockName),uuidValule,String.valueOf(expireTime))) {// 休眠60毫秒再来重试try {TimeUnit.MILLISECONDS.sleep(60);} catch (InterruptedException e) {e.printStackTrace();}}// 新建一个后台扫描程序,来检查Key目前的ttl,是否到我们规定的剩余时间来实现锁续期resetExpire();return true;}return false;
}// 自动续期
private void resetExpire() {String script ="if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " +"return redis.call('expire', KEYS[1], ARGV[2]) " +"else " +"return 0 " +"end";new Timer().schedule(new TimerTask() {@Overridepublic void run() {if (stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),Arrays.asList(lockName),uuidValule,String.valueOf(expireTime))) {// 续期成功,继续监听System.out.println("resetExpire() lockName:" + lockName + "\t" + "uuidValue:" + uuidValule);resetExpire();}}}, (this.expireTime * 1000 / 3));
}

总结

1.synchronized单机版OK;
2.Nginx分布式微服务,轮询多台服务器,单机锁不行;
3.取消单机锁,上redis分布式锁setnx,中小企业使用没问题;
4.​ 只是加锁了,没有释放锁,出异常的话,可能无法释放锁,必须要在代码层面finally释放锁
5.​ 如果服务宕机,部署了微服务代码层面根本就没有走到finally这块,没办法保证解锁,这个Key没有被删除,需要对锁设置过期时间 -
6.​ 为redis的分布式锁key增加过期时间,还必须要保证setnx+过期时间在同一行,保证原子性
7.​ 程序由于执行超过锁的过期时间,所以在finally中必须规定只能自己删除自己的锁,不能把别人的锁删除了,防止张冠李戴
8.将Lock、unlock变成LUA脚本保证原子性;
9.保证锁的可重入性,hset替代setnx+Lock变成LUA脚本,保障可重入性;
10.锁的自动续期 。

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

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

相关文章

执着追求与匠心独运 朵拉朵尚2024欧洲溯源 深入德国巴斯夫

执着追求与匠心独运 朵拉朵尚2024欧洲溯源 深入德国巴斯夫 前不久&#xff0c;朵拉朵尚踏上了其2024年欧洲溯源之旅的第三站—德国巴斯夫&#xff0c;一场旨在深化护肤智慧、共谋新品未来的深度交流盛宴在此拉开帷幕。作为全球最大的化工公司&#xff0c;巴斯夫不仅以其卓越的…

电脑录屏工具哪个好用?推荐新手几款实用工具介绍

现在不管是录个教学视频教教别人&#xff0c;还是直播游戏给粉丝看&#xff0c;或者是展示你的产品&#xff0c;都得用到它。但是市面上的录屏软件多得让人眼花缭乱&#xff0c;新手可能一看就懵了。别急&#xff0c;今天我就给你介绍几个特别好用的电脑录屏工具&#xff0c;不…

攻克大模型面试!RAG基础与应用痛点一网打尽!

RAG相关理论知识与经验整理。 谈到大模型在各垂直领域中的应用&#xff0c;一定离不开RAG&#xff0c;本系列开始分享一些RAG相关使用经验&#xff0c;可以帮助大家在效果不理想的时候找到方向排查或者优化。 本系列以医疗领域为例&#xff0c;用面试题的形式讲解RAG相关知识…

唤醒金融数据中台:我的数据驱动秘籍

目录 一、明析业务痛点和机会点二、数据驱动精准化营销三、一体化数据平台——整合金融数据1. 数据整合与标准化2. 数据服务与共享3.业务体系集中化 四、强化金融数据安全&#xff0c;筑牢数据保护防线 在当今数字化时代的大潮中&#xff0c;数据无疑是金融行业最耀眼的财富。作…

销售管理中的难题,你是否也深陷其中?

销售管理中的难题似乎总是让人头疼。从客户跟进不及时&#xff0c;到销售数据分析不足&#xff0c;再到团队沟通不畅&#xff0c;导致商机频频流失。销售目标总是难以达成&#xff0c;业绩压力越来越大&#xff0c;但解决方案却总是力不从心。面对这样的问题&#xff0c;你是不…

Chainlit集成LlamaIndex并使用通义千问模型实现AI知识库检索网页对话应用增强版

前言 之前使用Chainlit集成LlamaIndex并使用通义千问大语言模型的API接口&#xff0c;实现一个基于文档文档的网页对话应用。 可以点击我的上一篇文章《Chainlit集成LlamaIndex并使用通义千问模型实现AI知识库检索网页对话应用》 查看。 本次针对上一次的代码功能进一步的完善…

推动公平学习与身份归一化的视网膜神经疾病数据集

人工智能咨询培训老师叶梓 转载标明出处 在机器学习领域&#xff0c;公平性&#xff08;也称为公正性或平等性&#xff09;是一个日益受到关注的话题&#xff0c;它对于社会的福祉至关重要。然而&#xff0c;目前缺乏专门用于公平学习且包含成像数据的公共医学数据集&#xff…

分苹果 - 华为OD统一考试(E卷)

2024华为OD机试(E卷+D卷+C卷)最新题库【超值优惠】Java/Python/C++合集 题目描述 A 和 B 两个人要分苹果。A 希望按照他的计算规则得到平均分配的苹果,而 B 希望在满足 A 的条件下获得尽可能多的苹果量。 A 的计算规则是按照二进制加法进行,并不计算进位。例如,12 + 5 =…

CAT1 DTU软硬件设计开源资料分析(TCP协议版本 )

一、CAT1 DTU方案简介&#xff1a; 远程终端单元DTU&#xff0c;一种针对通信距离较长和工业现场环境恶劣而设计的具有模块化结构的、特殊的计算机测控单元&#xff0c;它将末端检测仪表和执行机构与远程控制中心相连接。 奇迹TCP DTU版本DTU&#xff0c;用于将远程现场的传感…

VoIP协议

VoIP协议是VoIP业务的规范标准。我们都知道VoIP业务有着压倒性的优势。随着网络应用的多元化和低成本化发展&#xff0c;VoIP业务直接冲击着传统通信市场&#xff0c;那么目前VoIP协议目前常用的协议,如H.323、SIP、MEGACO和MGCP。 H.248 H.248是定义网关控制协议的ITU建议书…

分布式光伏发电站数据采集设备管理硬件解决方案

随着全球能源结构的转型和可再生能源的快速发展&#xff0c;光伏发电作为重要的清洁能源之一&#xff0c;在能源市场中占据了越来越重要的位置。AcrelCoud-1200分布式光伏运维云平台通过监测光伏站点的逆变器设备&#xff0c;气象设备以及摄像头设备&#xff0c;帮助用户管理分…

【Linux系统编程】第二十弹---进程优先级 命令行参数 环境变量

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、进程优先级 2.1、什么是优先级 2.2、优先级的描述 2.3、优先级与权限的关系 2.4、为什么要有优先级 2.5、Linux优先级的…

某oa命令执行漏洞挖掘思路

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 《Java代码审…

图像修复(Inpainting)技术的前沿模型与数据集资源汇总

图像修复&#xff08;Image Inpainting&#xff09;是一种计算机视觉技术&#xff0c;旨在填补图像中的缺失区域或去除图像中的不需要部分&#xff0c;使其看起来自然且无明显痕迹。其目标是根据图像的上下文信息和周围像素来推断和重建缺失区域的内容&#xff0c;以生成逼真且…

《机器学习》周志华-CH7(贝叶斯分类)

7.1贝叶斯决策论 对分类任务而言&#xff0c;在所有相关概率已知的理想情形下&#xff0c;贝叶斯决策论考虑如何基于这些概率核误判损失来选择最优的类别标记。 R ( x i ∣ x ) ∑ j 1 N λ i j P ( c j ∣ x ) \begin{equation} R(x_{i}|x)\sum_{j1}^{N}\lambda_{ij}P(c_{j}…

DTMF2str集成工具

DTMF2str 项目地址&#xff1a; baicaiyihao/DTMF2str: DTMF解码并转换为字符串 (github.com) 用于CTF中misc DTMF题中&#xff0c;方便ctfer解题。 注&#xff1a;工具根据现有的一些题型进行编写的&#xff0c;如果有无法解出的题型可以提issus后续进行完善。 工具功能&a…

C++独立开发开源大数计算库 CBigNum

项目简介&项目地址 CBigNum 是本人独立开发开源的一款大数计算库&#xff0c;支持任意位数整数带任意位数小数的浮点运算。您可以通过本库执行非常大的数据运算或非常高精度的除法运算(您可以随意指定除法的小数保留到第几位)。 项目地址&#xff1a;https://github.com/…

MATLAB路径规划如何采用矢量法让他们尽量在一个方向??

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

HTML | 外部引入 CSS 的2种方式:link和@import有什么区别?

外部引入 CSS 有2种方式&#xff0c;link 和 import。就结论而言&#xff0c;强烈建议使用 link &#xff0c;慎用 import 方式。 两者都是外部引用 CSS 的方式&#xff0c;但是存在一定的区别&#xff1a; &#xff08;1&#xff09;从属关系区别 link是HTML / XHTML标签&a…

误删分区后的数据救赎恢复实战解析

在数字化时代&#xff0c;数据不仅是信息的载体&#xff0c;更是个人记忆与企业资产的宝贵财富。然而&#xff0c;误删分区这一操作失误&#xff0c;却如同暗流涌动&#xff0c;悄无声息地吞噬着用户的重要数据。本文将深入探讨误删分区的现象、影响&#xff0c;并详细介绍一种…