Redis设计与实现 学习笔记 第二十二章 二进制位数组

Redis提供了SETBIT、GETBIT、BITCOUNT、BITOP四个命令用于处理二进制位数组(bit array,又称“位数组”)。

SETBIT命令用于为位数组指定偏移量上的二进制位设置值,位数组的偏移量从0开始,而二进制位的值可以是0或1:
在这里插入图片描述
GETBIT命令用于获取位数组指定偏移量上的二进制位的值:
在这里插入图片描述
BITCOUNT命令用于统计位数组里,值为1的二进制位的数量:
在这里插入图片描述
在这里插入图片描述
BITOP命令可对多个位数组进行按位与、按位或、按位异或运算:
在这里插入图片描述
BITOP命令也可对给定位数组进行取反:
在这里插入图片描述
22.1 位数组的表示

Redis使用字符串对象来表示位数组,因为字符串对象是用的SDS数据结构是二进制安全的,所以程序可以直接使用SDS结构来保存位数组,并使用SDS结构的操作函数来处理位数组。

图22-1展示了用SDS表示的,一字节长的位数组:
在这里插入图片描述
上图中:
1.redisObject.type的值为REDIS_STRING,表示这是一个字符串对象。

2.sdshdr.len的值为1,表示这个SDS保存了一个一字节长的字符串,用来表示位数组。

3.buf数组中的buf[0]字节保存了一字节长的位数组。

4.buf数组中的buf[1]字节保存了SDS程序自动追加到值末尾的空字符’\0’。

为了清晰地展示各个位的值,本章会对SDS中buf数组的展示方式进行修改,让各个位都可以清楚地展现出来,例如:
在这里插入图片描述
现在,buf数组的每个字节都用一行来表示,每行的第一个格子buf[i]表示这是buf数组的第i个字节,而buf[i]后的八个格子分别代表这一字节中的八个位。

图22-2中, buf[0]字节的各个位的值分别是10110010,是按逆序(小端字节序)保存的,左边是低位,右面是高位。使用逆序来保存位数组可以简化SETBIT命令的实现,稍后会介绍到。

图22-3展示了另一个位数组例子:
在这里插入图片描述
1.sdshdr.len属性的值为3,表示这个SDS保存了一个三字节长的位数组。

2.位数组由buf数组中的buf[0]、buf[1]、buf[2]三个字节保存,同样也是逆序保存的,上图位数组大端字节序表示是:11110000 11000011 10100101。

22.2 GETBIT命令的实现

GETBIT命令用于返回位数组bitarray在offset偏移量上的二进制位的值:
在这里插入图片描述
GETBIT命令的执行过程如下:
1.计算byte=int(offset/8),byte值记录了offset偏移量指定的二进制位在位数组的哪个字节。

2.计算bit=offset%8+1,bit值记录了offset偏移量指定的二进制位是byte字节的第几个二进制位。

3.根据byte和bit值,在位数组bitarray中定位offset偏移量指定的二进制位,并返回这个位的值。

对于图22-2所示的位数组来说,命令:
在这里插入图片描述
将执行以下操作:
1.int(3/8)的值为0。

2.(3%8)+1的值为4。

3.定位到buf[0]字节上,然后取出该字节上的第4个二进制位的值(小端序,从左往右数)。

4.向客户端返回二进制位的值1。

命令执行过程如图22-4所示:
在这里插入图片描述
GETBIT命令的所有操作都可以在常数时间内完成,所以该命令的时间复杂度为O(1)。

22.3 SETBIT命令的实现

SETBIT用于将位数组bitarray在offset偏移量上二进制位的值设为value,并向客户端返回二进制位被设置前的旧值:
在这里插入图片描述
以下是SETBIT命令的执行过程:
1.计算len=int(offset/8)+1,len记录了保存offset偏移量指定的二进制位至少需要多少字节。

2.检查bitarray键保存的位数组(即SDS)的长度是否小于len,如果是,将SDS的长度扩展为len字节,并将所有新扩展空间的二进制位的值设为0。

3.计算byte=int(offset/8),byte值记录了offset偏移量指定的二进制位保存在位数组的哪个字节。

4.计算bit=offset%8+1,bit值记录了offset偏移量指定的二进制位是byte字节的第几个二进制位。

5.根据byte和bit值,在bitarray键保存的位数组中定位offset偏移量指定的二进制位,首先将指定二进制位的当前值保存在oldvalue变量,然后将这个二进制位的值设为新值。

6.向客户端返回oldvalue变量的值。

SETBIT命令的所有操作都可以在常数时间内完成,所以该命令的时间复杂度为O(1)。

22.3.1 SETBIT命令的执行示例

我们对图22-2所示的位数组执行命令:
在这里插入图片描述
那么服务器将执行以下操作:
1.计算int(1/8)+1,得出值1,表示保存偏移量为1的二进制位至少需要1字节长的位数组。

2.检查位数组长度,发现SDS的长度不小于1字节,无须执行扩展操作。

3.计算int(1/8),得出值0,说明偏移量1的二进制位位于buf[0]字节。

4.计算1%8+1,得出值2,说明偏移量为1的二进制位是buf[0]字节的第2个二进制位。

5.定位到buf[0]字节的第2个二进制位上,将二进制位现在的值0保存到oldvalue变量,然后将二进制位的值设为1。

6.向客户端返回oldvalue变量的值0。

图22-6展示了SETBIT命令的执行过程:
在这里插入图片描述
图22-7展示了SETBIT命令执行后,位数组的样子:
在这里插入图片描述
22.3.2 带扩展操作的SETBIT命令示例

现在我们看一个需要对位数组进行扩展的SETBIT命令的例子。

假设我们对图22-2所示的位数组执行命令:
在这里插入图片描述
那么服务器将执行以下操作:
1.计算int(12/8)+1,得出值2,这表示保存偏移量12的二进制位至少需要2字节长的位数组。

2.对位数组的长度进行检查,得知位数组现在的长度为1字节,这比执行命令所需的2字节要小,所以程序会要求将位数组的长度扩展为2字节。尽管程序只要求2字节长的位数组,但SDS的空间预分配策略会为SDS额外多分配2字节的未使用空间,再加上为保存空字符而额外分配的1字节,扩展后的buf数组的实际长度为5字节,如图22-8所示:
在这里插入图片描述
3.计算int(12/8),得出值1,说明偏移量为12的二进制位位于buf[1]字节中。

4.计算12%8+1,得出值5,说明偏移量为12的二进制位是buf[1]字节的第5个二进制位。

5.定位到buf[1]字节的第5个二进制位,将二进制位现在的值0保存到oldvalue变量,然后将二进制位的值设为1。

6.向客户端返回oldvalue变量的值0。

图22-9展示了SETBIT命令定位并设置二进制位的过程:
在这里插入图片描述
图22-10展示了SETBIT命令执行后,位数组的样子:
在这里插入图片描述
因为buf数组使用逆序来保存位数组,所以当程序对buf数组进行扩展后,不必改动位数组原有的二进制位。

相反地,如果buf数组使用和书写位数组时一样的顺序(大端序)来保存位数组,那么在每次扩展buf数组后,程序都需要将位数组已有的位进行移动,然后才能执行写入操作,这次SETBIT命令目前的实现方式要复杂,并且移位带来的CPU时间消耗也会影响命令的执行速度。

图22-11至图22-14模拟了程序在buf数组按书写顺序保存位数组的情况下,对位数组0100 1101执行命令BITSET <bitarray> 12 1,将值改为0001 0000 0100 1101的整个过程:
在这里插入图片描述
22.4 BITCOUNT命令的实现

BITCOUNT命令用于统计给定位数组中,值为1的二进制位的数量。

例如,对于图22-15所示的位数组来说,BITCOUNT命令将返回4:
在这里插入图片描述
接下来对BITCOUNT命令可能使用的几种算法进行介绍,并最终给出BITCOUNT命令的具体实现。

22.4.1 二进制位统计算法(1):遍历算法

实现BITCOUNT命令最简单直接的方法,就是遍历数组中的每个二进制位,并在遇到值为1的二进制位时,将计数器的值增1。

遍历算法虽然实现简单,但效率非常低,因为这个算法在每次循环中只能检查一个二进制位的值是否为1,所以检查操作的执行次数与数组包含的二进制位的数量成正比。

22.4.2 二进制位统计算法(2):查表算法

优化检查操作的一个办法是使用查表法:
1.对于一个有限集合来说,集合元素的排列方式是有限的。

2.对于一个有限长度的位数组来说,它能表示的二进制位排列也是有限的。

根据这个原理,我们可以创建一个表,表的键是某种排列的位数组,而表的值是相应位数组中,值为1的二进制位数量。

创建了这种表后,我们就可以根据输入的位数组进行查表,从而在无须对位数组的每个位进行检查的情况下,直接知道这个位数组包含了多少个值为1的二进制位。

例如,对于8位长的位数组来说,我们可以创建表格22-1:
在这里插入图片描述
通过上表,我们可以一次从位数组中读入8个位,然后根据这8个位的值进行查表,直接知道这个值包含了多少个值为1的位,和之前介绍的算法相比,查表法的效率提升了8倍。

如果我们创建一个更大的表,那么一次查表能处理的位数就会更多,从而减少查表操作的次数。

看起来,只要我们创建一个足够大的表,那么统计工作就能轻易完成,但实际没有那么简单,因为查表法的实际效果会收到内存和缓存的限制:
1.查表法是典型的空间换时间策略,节约的时间越多,花费的内存越大。创建键长为8位的表仅需数百个字节,创建键长为16位的表也仅需数百KB,但创建键长为32位的表却需要十多GB。在实际中,服务器只可能接受数百字节或数百KB的内存消耗。

2.查表法的效果还受到CPU缓存的限制,对于固定大小的CPU缓存来说,创建的表越大,CPU缓存能保存的内容相比整个表格的比例就越少,查表时出现缓存不命中(cache miss)的情况就越多,缓存的换入和换出操作就会越频繁,最终影响查表法的实际效率。

由于以上两个原因,我们可以得出结论,查表法是一种比遍历算法更好的统计办法,但受限于查表法带来的内存压力,以及缓存不命中可能带来的影响,我们只能考虑创建键长为8或16位的表,而这两种表带来的效率提升,对于处理非常长的位数组来说仍然远远不够。

22.4.3 二进制位统计算法(3):variable-precision SWAR算法

BITCOUNT命令要解决的问题——统计一个位数组中非0二进制位的数量,在数学上被称为“计算汉明重量(Hamming Weight)”。

因为汉明重量常被用于信息论、编码理论、密码学,所以研究人员针对计算汉明重量开发了多种不同算法,一些处理器甚至直接带有计算汉明重量的指令,而对于不具备这种特殊指令的普通处理器来说,目前已知效率最好的通用算法为variable-precision SWAR算法,该算法通过一系列位移和位运算操作,可以在常数时间内计算多个字节的汉明重量,并且不需要使用额外内存。

以下是一个处理32位长度位数组的variable-precision SWAR算法的实现:

uint32_t swar(uint32_t i) {// 步骤1i = (i & 0x55555555) + ((i >> 1) & 0x55555555);// 步骤2i = (i & 0x33333333) + ((i >> 2) & 0x33333333);// 步骤3i = (i & 0x0F0F0F0F) + ((i >> 4) & 0x0F0F0F0F);// 步骤4i = (i*(0x01010101) >> 24);return i;
}

步骤1:
i = (i & 0x55555555) + ((i >> 1) & 0x55555555);
这一步中,掩码0x55555555(二进制形式为 01010101010101010101010101010101)用于选取i中所有奇数位置的位。
(i >> 1) & 0x55555555则是选取所有偶数位置的位。
然后将这两个结果相加,这样每两位中的1的个数就被计算出来并存放在原来的两位中。

步骤2:
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
掩码0x33333333(二进制形式为 00110011001100110011001100110011)用于选取每四位中的前两位。
(i >> 2) & 0x33333333用于选取每四位中的后两位。
通过相加,现在每四位存储的是原始四位中1的总数。

步骤3:
i = (i & 0x0F0F0F0F) + ((i >> 4) & 0x0F0F0F0F);
掩码0x0F0F0F0F(二进制形式为 00001111000011110000111100001111)用于选取每八位中的前四位。
(i >> 4) & 0x0F0F0F0F用于选取每八位中的后四位。
通过相加,现在每八位存储的是原始八位中1的总数。

步骤4:
i = (i*(0x01010101) >> 24);
乘以0x01010101(二进制形式为 00000001000000010000000100000001)将i的每个字节的值累加到最高字节上。举个例子,abcd efgh乘0001 0001,相当于abcd efgh*(1+1 0000),结果取后8位,后8位的最高4位值为abcd+efgh。
最后通过右移24位,就可以得到整个32位数中1的总数量。

例如,对于调用swar(0x3A70F21B),程序在第一步将计算出值0x2560A116,这个值的每两个二进制位记录了0x3A70F21B每两个二进制位的汉明重量,如表22-2所示:
在这里插入图片描述
之后,程序在第二步将计算出值0x22304113,这个值的每四个二进制位记录了0x3A70F21B每四个二进制位的汉明重量,如表22-3所示:
在这里插入图片描述
接下来,程序在第三步将计算出值0x4030504,这个值的每八个二进制位记录了0x3A70F21B每八个二进制位的汉明重量,如表22-4所示:
在这里插入图片描述
在第四步,程序首先计算0x4030504 * 0x01010101 = 0x100c0904,将汉明重量聚集到二进制位的最高八位,如表22-5所示:
在这里插入图片描述
之后程序计算0x100c0904 >> 24,将汉明重量移动到低八位,最终得出值0x10,即十进制值16,这个值就是0x3A70F21B的汉明重量,如表22-6所示:
在这里插入图片描述
swar函数每次执行可计算32个二进制位的汉明重量,它比之前介绍的遍历算法快32倍,比键长为8位的查表法快4倍,比键长为16位的查表法快2倍,并且因为swar函数是单纯的计算操作,所以它无须像查表法那样,使用额外的内存。

因为swar函数是一个常数复杂度的操作,所以我们可以根据自己的需要,在一次循环中多次执行swar,从而按倍数提升计算汉明重量的效率(这段有点问题,一次循环中调用多次swar函数也不会减少任何计算量,不会提升效率,这种方法只能减少循环次数,相当于循环展开,循环5次改写成把循环体复制5次,意义不大):
1.例如,如果我们在一次循环中调用两次swar函数,那么计算汉明重量的效率就从之前的一次循环计算32位提升到了依次循环计算64位(错误的)。

2.又例如,如果我们在一次循环中调用四次swar函数,那么一次循环就可以计算128个二进制位的汉明重量,这次每次循环调用一次swar函数要快四倍(错误的)。

22.4.4 二进制位统计算法(4):Redis的实现

BITCOUNT命令的实现用到了查表和variable-precision SWAR两种算法:
1.查表算法使用键长为8位的表,表中记录了从0000 0000到1111 1111在内的所有二进制位的汉明重量。

2.至于variable-precision SWAR算法方面,BITCOUNT命令在每次循环中载入128个二进制位,然后调用四次32位variable-precision SWAR算法来计算这128个二进制位的汉明重量。

在执行BITCOUNT命令时,程序会根据未处理的二进制位的数量来决定使用哪种算法:
1.如果未处理的二进制位数量大于等于128位,那么程序使用variable-precision SWAR算法来计算二进制位的汉明重量。

2.否则,程序使用查表法计算二进制位的汉明重量。

以下伪代码展示了BITCOUNT命令的实现原理:

# 一个表,记录了所有八位长位数组的汉明重量
# 程序将8位长的位数组转换成无符号整数,并在表中进行索引
# 例如,对于输入0000 0011,程序将二进制转换为无符号整数3
# 然后取出weight_in_byte[3]的值2
# 2就是0000 0011的汉明重量
weight_in_byte = [0,1,1,2,1,2,2,/*...*/,7,7,8]def BITCOUNT(bits):# 计算位数组包含了多少二进制位count = count_bit(bits)# 初始化汉明重量为零weight = 0# 如果未处理的二进制位大于等于128位# 那么使用variable-precision SWAR算法来处理while count >= 128:# 四个swar调用,每个调用计算32个二进制位的汉明重量# 注意:bits[i:j]中的索引j是不包含在取值范围内的weight += swar(bits[0:32])weight += swar(bits[32:64])weight += swar(bits[64:96])weight += swar(bits[96:128])# 移动指针,略过已处理的位,指向未处理的位bits = bits[128:]# 减少未处理位的长度count -= 128# 如果执行到这里,说明未处理的位数量不足128位# 那么使用查表法来计算汉明重量while count:# 将8个位转换成无符号整数,作为查表的索引(键)index = bits_to_unsigned_int(bits[0:8])weight += weight_in_byte[index]# 移动指针,略过已处理的位,指向未处理的位bits = bits[8:]# 减少未处理位的长度count -= 8# 计算完毕,返回输入二进制位的汉明重量return weight

这个BITCOUNT实现的算法复杂度为O(n),其中n为输入二进制位的数量。更具体一点,第一个循环是消耗时间的大头,n位的二进制位需要循环n/32次,一次循环的时间复杂度为O(1),因此总时间复杂度为O(n)。

22.5 BITOP命令的实现

因为C语言直接支持对字节执行逻辑与(&)、逻辑或(|)、逻辑异或(^)、逻辑非(~)操作,所以BITOP命令的AND、OR、XOR、NOT四个操作都是直接基于这些逻辑操作实现的:
1.执行BITOP AND命令时,程序用&操作计算出所有输入二进制位的逻辑与结果,然后保存在指定键上。

2.执行BITOP OR命令时,程序用|操作计算出所有输入二进制位的逻辑与结果,然后保存在指定键上。

3.执行BITOP XOR命令时,程序用^操作计算出所有输入二进制位的逻辑与结果,然后保存在指定键上。

4.执行BITOP NOT命令时,程序用~操作计算出所有输入二进制位的逻辑与结果,然后保存在指定键上。

例如,客户端执行命令:
在这里插入图片描述
其中,键x保存的位数组如图22-18所示,而键y保存的位数组如图22-19所示:
在这里插入图片描述
BITOP命令将执行以下操作:
1.创建一个空白位数组value,用于保存AND操作的结果。

2.对两个位数组的第一个字节执行buf[0] & buf[0]操作,并将结果保存到value[0]字节。

3.对两个位数组的第二个字节执行buf[1] & buf[1]操作,并将结果保存到value[1]字节。

4.对两个位数组的第三个字节执行buf[2] & buf[2]操作,并将结果保存到value[2]字节。

5.经过前面三次逻辑与操作,程序得到了图22-20所示的计算结果,并将它保存到result键上面:
在这里插入图片描述
22.6 重点回顾

1.Redis使用SDS来保存位数组。

2.SDS使用逆序来保存位数组,这种保存顺序简化了SETBIT命令的实现,使得SETBIT命令可以在不移动现有二进制位的情况下,对位数组进行空间扩展。

3.BITCOUNT命令使用了查表算法和variable-precision SWAR算法来优化命令的执行效率。

4.BITOP命令的所有操作都使用C语言内置的位操作来实现。

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

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

相关文章

Linux服务器安装Linux宝塔面板部署wordpress网站以及雷池WAF

一、Linux服务器安装Linux宝塔面板 这个步骤参考网上其他教程。 二、Linux宝塔面板部署wordpress网站 这个步骤参考网上其他教程&#xff0c;保证网站能够正常访问&#xff0c;并且使用Linux宝塔面板申请并部署了SSL证书&#xff0c;使用https协议正常访问。 三、Linux宝塔…

C++学习日记---第14天(蓝桥杯备赛)

笔记复习 1.对象的初始化和清理 对象的初始化和清理是两个非常重要的安全问题&#xff0c;一个对象或者变量没有初始状态&#xff0c;对其使用后果是未知&#xff0c;同样的使用完一个对象或者变量&#xff0c;没有及时清理&#xff0c;也会造成一定的安全问题 构造函数&…

算法编程题-煎饼排序 不含AAA或者BBB的字符串

算法编程题-煎饼排序 &&不含AAA或者BBB的字符串 煎饼排序原题描述思路简述代码实现复杂度分析 不含AAA或者BBB的字符串原题描述思路简述代码实现复杂度分析 摘要&#xff1a;本文将对两道LeetCode原题进行介绍&#xff0c;分别是煎饼排序和不含AAA或者BBB的字符串。在陈…

【C/C++】内存管理详解:从new/delete到智能指针的全面解析

文章目录 更多文章C/C中的传统内存管理方式new和delete运算符malloc和free函数传统内存管理的弊端 智能指针的崛起智能指针的定义与作用C11引入的标准智能指针 详解C标准智能指针std::unique_ptr特点使用方法适用场景 std::shared_ptr特点使用方法适用场景 std::weak_ptr特点使…

Vue进阶之单组件开发与组件通信

书接上篇&#xff0c;我们了解了如何快速创建一个脚手架&#xff0c;现在我们来学习如何基于vite创建属于自己的脚手架。在创建一个新的组件时&#xff0c;要在新建文件夹中打开终端创建一个基本的脚手架&#xff0c;可在脚手架中原有的文件中修改或在相应路径重新创建&#xf…

深度学习的python基础(1)

一.tensor创建 1.张量的定义 张量在形式上就是多维数组&#xff0c;例如标量就是0维张量&#xff0c;向量就是一维张量&#xff0c;矩阵就是二维张量&#xff0c;而三维张量就可以想象RGB图片&#xff0c;每个channel是一个二维的矩阵&#xff0c;共有三个channel&#xff0…

scss文件内引入其他scss文件报错

在 Sass (SCSS) 中&#xff0c;import 语句用于在当前文件中导入其他 Sass 文件&#xff0c;以便你可以重用样式和变量等。然而&#xff0c;从 Dart Sass 1.23.0 版本开始&#xff0c;import 语句已经被标记为弃用&#xff08;deprecated&#xff09;&#xff0c;并计划在未来的…

Unity3D模型场景等测量长度和角度功能demo开发

最近项目用到多段连续测量物体长度和角度功能&#xff0c;自己研究了下。 1.其中向量角度计算&#xff1a; 需要传入三个坐标来进行计算。三个坐标确定两条向量线段的方向&#xff0c;从而来计算夹角。 public Vector3 SetAngle(Vector3 p1, Vector3 p2,Vector3 p3) { …

【MATLAB】基于RSSI的蓝牙定位与例程,设置4个基站、二维定位

目录 ​编辑 商品描述 主要功能 技术细节 适用场景 下载链接 商品描述 这款基于接收信号强度指示&#xff08;RSSI&#xff09;原理的蓝牙定位程序&#xff0c;专为需要高效、可靠定位解决方案的开发者和研究人员设计。它能够在二维平面内&#xff0c;通过4个锚点实现对未…

【学习笔记】基于RTOS的设计中的堆栈溢出(Stack Overflow)-第1部分

本文由RTOS专家Jean J. Labrosse撰写。 基于RTOS的应用程序中的每个任务都需要自己的堆栈,堆栈的大小取决于任务的要求(例如,函数调用嵌套、传递给函数的参数、局部变量等)。 为了避免堆栈溢出,开发人员需要过度分配堆栈空间,但不要太多,以避免浪费RAM。 什么是堆栈溢…

ROC曲线

文章目录 前言一、ROC的应用&#xff1f;二、使用方式1. 数据准备2.绘图可视化 前言 在差异分析中&#xff0c;ROC曲线可以用来评估不同组之间的分类性能差异。差异分析旨在比较不同组之间的特征差异&#xff0c;例如在基因表达研究中比较不同基因在不同条件或组织中的表达水平…

基数排序(代码+注释)

#include <stdio.h> #include <stdlib.h>// 获取数组中的最大值 int GetMax(int* a, int n) {int max a[0];for (int i 1; i < n; i) {if (a[i] > max) {max a[i];}}return max; }// 对数组按照某个位数进行计数排序 void CountingSortForRadix(int* a, i…

第一届“吾杯”网络安全技能大赛 Writeup

战队信息 战队名称&#xff1a;在你眼中我是誰&#xff0c;你想我代替誰&#xff1f; 战队排名&#xff1a;13 Misc Sign Hex 转 Str&#xff0c;即可得到flag。 原神启动&#xff01; 不好评价&#xff0c;stegsolve 秒了&#xff1a; WuCup{7c16e21c-31c2-439e-a814-b…

AJAX一、axios使用,url组成(协议,域名,资源路径)查询参数和化简,错误处理,请求/响应报文,状态码,接口文档,

一、AJAX是什么 概念 &#xff1a; AJAX是一种与服务器&#xff08;后端&#xff09;通信的技术 二、请求库axios的基本用法 1导包 2使用 // 1. 发请求 axios({ url: 请求地址 }).then(res > { // 2.接收并使用数据 }) <body><p class"province"…

基于 Python、OpenCV 和 PyQt5 的人脸识别上课打卡系统

大家好&#xff0c;我是Java徐师兄&#xff0c;今天为大家带来的是基于 Python、OpenCV 和 PyQt5 的人脸识别上课签到系统。该系统采用 Python 语言开发&#xff0c;开发过程中采用了OpenCV框架&#xff0c;Sqlite db 作为数据库&#xff0c;系统功能完善 &#xff0c;实用性强…

智能化业务校验框架:动态设计与应用实践

目录 一、业务背景 二、配置内容展示 三、商品动态配置内容展示 &#xff08;一&#xff09;商品spu校验信息数据 &#xff08;二&#xff09;商品sku校验信息数据 &#xff08;三&#xff09;组包商品校验信息数据 &#xff08;四&#xff09;商品数据校验数据持有者 &…

idea2024加载flowable6.8.1.36遇到的问题-idea启动flowable问题flowable源码启动问题

代码下载地址&#xff1a; https://gitee.com/hanpenghu_admin_admin/flowable6.8.1.git 1.首先是通过顶层目录maven clean install 发现很多子模块并不会install本地mavenStore库&#xff0c;这导致了&#xff0c;一堆相互依赖的模块报错找不到&#xff0c;所以需要根据报错…

web安全从0到1:burp-suite3

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

深度学习模型: BERT(Bidirectional Encoder Representations from Transformers)详解

一、引言 自然语言处理&#xff08;NLP&#xff09;领域在过去几十年取得了显著的进展。从早期基于规则的方法到统计机器学习方法&#xff0c;再到如今基于深度学习的模型&#xff0c;NLP 不断向着更高的准确性和效率迈进。BERT 的出现为 NLP 带来了新的突破&#xff0c;它能够…

ESP8266 (ESP-01S)烧录固件 和 了解与单片机通信必需的AT指令

ESP8266&#xff08;ESP-01s&#xff09;烧录固件 工具&#xff1a; 需要安装的原装出厂固件库&#xff1a; ESP8266 --接线-- VCC 3.3&#xff08;外接开发板&#xff09; GND GND&#xff08;外接开发板&#xff09; IO0 GND&#xff08;外接…