ES入门十一:正排索引和倒排索引

news2024/11/27 6:27:20

索引本质上就是一种加快检索数据的存储结构,就像书本的目录一下。

为了更好的理解正排索引和倒排索引,我们借由一个 **唐诗宋词比赛,**这个比赛一共有两个项目:

  1. 给定诗词名称,背诵整首
  2. 给诗词中几个词语,让你说出带这些词语的诗词。

不难想到,1比较简单,就是一个正向索引,2比较难,属于逆向索引

正排索引

如果想赢得第一个项目,我们怎么设计,我们可以把诗词名作为key,然后诗词内容作为value,然后放到hash中存储起来。像这种我们吧实体id到数据内容实体的关联关系的索引我们称之为正排索引

image.png

倒排索引

但是如果说我们想通过词语来找出哪些诗词中含有这些诗词,这个怎么做哪?不能用hash这种来存储了,这里就可以考虑倒排索引,

  1. 将所有诗词的内容进行分词
  2. 建立各个词语到诗词名称的索引

image.png
根据倒排索引,我们可以

  1. 获取诗词(词项)对应的文档id
  2. 如果有多个诗词(词项)也就会获取到多个文档id,然后做交集即可。
  3. 最后通过交集的文档id去文档里面找

用hash行不行

image.png
如上图,我们看起来用hash实现也是可以的,但是哪,如果数据量非常大,你想想诗词中取这个key得多少,更别说其他需求了。所以简单的使用hash是不行的,因为在存储海量数据的时候,系统将会面临下面这些问题:

  1. 分词形成的词项(term)可能是海量的,需要可以在内存和磁盘上高效存储
  2. 既然词项是海量的,那么如何快速找到对应的词项也是个问题
  3. 每个词项对应的文档可能非常多,也就是上图中的文档列表很长
  4. 在词项对应的文档多的情况下,多个文档列表做交集也是一个很大的问题

是不是感觉很难?
其实就是词项和文档列表的问题

  1. Lucene的倒排索引的实现中,使用词项索引(Term Index)解决了问题1和问题2
  2. Lucene使用Roaring Bitmaps、跳表等技术解决了问题3和问题4

倒排索引的原理

倒排索引的组成一共有3个部分:

  1. Term Index
  2. Term Dictionary
  3. Posting List

image.png
Term Dictionary放的是词项,如果词项越来越多查询肯定不行,所以就有了**Term Index,它是Term Dictionary的索引,最好设计得越小越好,这样缓存在内存中也没有压力。**Posting LIst保存了每个词项对应的文档Id列表

Segment

在了解 Term Index、Term Dictionary、Posting List 的具体实现前,我们先来看看 Segment、文档、Field、Term 与它们的关系。

默认情况下,ES每秒会把缓存中的数据写入到Segment,然后根据某些规则进行刷盘,并且合并这些Segment。所以Segment的数据一旦写入就不变了,采用不变形有2个好处:

  1. 更新对磁盘来说不友好
  2. 一个可变的数据有并发写的问题

逻辑上,一个Segment上会有多个文档,一个文档有多个字段。如下图中的Segment就有两个文档,文档1和文档2都有两个Field。这些字段的内容会被分词器形成多个Term,然后以块的形式保存到Term Dictionary中,并且系统会对Term Dictionary的内容做索引形成Term Index。在搜索的时候,通过

  1. Term Index找到Block
  2. 之后进一步找到Tem对应的Posting List中的文档Id
  3. 然后计算出符合条件的文档Id列表

需要注意的是,Segment中的每个字段(Field)都会有自己的Term Index、Term Dictionary、Posting List结构,也就是说每个Field中的这些结构都是独立的
image.png

Term Index的实现

Term Index作为Term Dictionary的索引,其最好是资源消耗小,可以缓存在内存中,而且数据查找有较低的复杂度。在讨论Tem Index如何实现前,看下面这几个词语:
image.png
这两对词语分别有公共的前缀:co和do。如果我们把Term Dictionary中的Term排序后按公共前缀抽取出来按块存储,而Term Index只使用公共前缀做索引,那本身要存储coach、cottage两个字符串的索引,现在只需要存储co一个就行了。这样的话,拥有同一个公共前缀的Term越多,实际上就是越省空间,并且这种设计在查找的时候 复杂度是前缀公共的长度:0(length(prefix))

但是这样的索引也有缺点,它只能找到公共前缀所在的块的地址,所以它既无法判断这个Term是否存在,也不知道这个Temr保存在Term Dictionary(.tim)文件具体在什么位置上。
image.png
如上图,对于在每个块中如何快速查找到对应的Term,我们可以使用二分法来搜索,因为Block中的数据是有序的。但机智的你是不是也发现可以优化的地方?因为前缀在Term Index中保存了,那么Block中就不要为每个Term在保存对应的前缀了,所以Block中保存的每个Term都可以省略掉前缀了。比如,co 前缀的 block1 中保存的内容为 ach、ttage 即可。

**对于前缀索引的实现,业界使用了FST 算法来解决。**FST(Finite State Transducers)是一种 FSM(Finite State Machines,有限状态机),并且有着类似于 Trie 树的结构。下面来简单了解一下 FST。

FST有以下特点:

  • 通过对Term Dictionary数据的前缀复用,压缩了存储空间
  • 高效的查询性能,O(len(prefix))的复杂度;
  • 构建后不可变。(事实上,倒排索引一旦生成就不可变了。)

下图是一个简单的FST图。从图中可以看出,一条边有两个元素label和out,其中label为key的元素,例如cat这个key 就有 c、a、t 这三个元素,out为此边的值value。小圆圈为FST的一个节点,节点分为两类-普通节点和Final节点,图中灰黑色的为Final节点,Final节点中还有Finalout,代表 Final 节点的值
image.png
当访问cat的时候,读取 c、a、t 的值作和, 再加上 Final 节点的 Finalout 值即可:10 + 0 + 5 + 0 = 15;当访问 dog 的时候,读取 d、o、g 的值即可,但是由于 g 为 Final,所以需要加上 g 的 Finalout:1 + 0 + 0 + 1 = 2。

简单的可以理解为这个: 不需要看上面ABC这些大的节点,直接看边,cat就是c+a+t,另外看这个t是不是final节点,如果dog的话还需要加上g为final out =1

Term Dictionary的实现

在倒排索引组成的3个部分中,Term dictionary保存了Term和Posting List的关系,存储了Term相关的信息, 比如记录该Term的文档数量、Term在Segment中出现的频率,还保存了指向Posting List的指针(文档列表的id的位置、词频位置等),

如下图,在对Term Dictionary 做索引的时候,先将所有的Term进行排序,然后将Term Dictionary中有共同前缀的Term抽取出来进行存储,再对共同前缀索引,最后通过索引就可以找到公共前缀对应的块在Term Dictionary文件中的偏移地址。

image.png
由于每个块中都有共同前缀,所以不需要再保存每个Term的全部内容,只需要保存其后缀即可,而且这些后缀都是排好序的。
image.png
至此,我们就可以通过Term Index 和 Term Dictionary 快速判断一个 Term 是否存在,并且存在的时候还可以快速找到对应的 Posting List 信息

Posting List的实现

上面的Posting List并不是只是包含文档ID,其实Posting List包含的信息比较多,如文档Id、词频、位置等等。Lucence把这些数据分成三个文件进行存储:

  • .doc文件,记录了文档Id信息和Term的词频,还记录了跳跃表信息,用来加速文档Id的查询;还记录了Term在pos和pay文件中的位置,有助于进行快速读取
  • .pay文件,记录了Payload信息和Term在doc中的偏移信息;
  • .pos文件,记录了Term在doc中的位置信息

因为倒排索引的主要目的就是找到文档id,所以下面讨论Posting List的时候我们就关注文档Id的信息,其他的就忽略了。前面提到过,Posting List主要面临2个问题:

  • 如果节省存储
  • 如何快速做交集

要节省存储,可以对数据进行压缩存储;至于如何做交集,业界应用的得比较多的是跳表、Roaring Bitmaps等技术。下面我们就针对这两个问题以及对应的方法分别讲解一下:

节省存储:整形压缩

不知道你有没有留意过,其实int类型或者long类型是可以进行压缩的。我们可以根据需要处理的数据的取值范围,选择占用更小字节数的数据类型来存储原数据,例如下面的Int数组:

# Int数组
[8, 12, 100, 140]

# 各个元素与其二进制表示
8 -> 00001000
12 -> 00001100
100 -> 01100100
140 -> 10001100

这个Int数组,如果每个数据都需要用4个字节来保存的话,需要16个字节。但你会发现,数组中最大的数140只需要用8位(一个字节)就可以表示了。这样一来,原来需要16个字节的数据只需要4个字节就可以表示了。

整数压缩基本就是这个思路了,当然还有其他玩法(下面介绍的VintBlock),但核心的思想都是:用最少的位数来表示原数据,并且兼顾数据读取效率。Lucene在Posting List中使用了2种编码格式来对整形类型的数据进行压缩- PackedBlock和VinBlock,并且在对整数进行压缩的基础上,还会对文档Id使用差值存储的方式来对数据进一步的压缩处理。

  • PackedBlock

在Lucene中,每当处理完128个包含某个Term的文档是,就会将这个文档Id和词频使用的PackedInts进行压缩存储,生成一个PackedBlock。PackedBlock中使用两个数组,一个是存储文档id的,一个是存储词频的。所以PackedBlock是存储固定长度的整形数组的结构,且数组的长度为128.

下面简单介绍一下PackedInts的实现,PackedInts会将Int[] 压缩打包成一个Block,其压缩方式是把数组中最大的那个数占用的有效位数作为标准,然后各个元素打包的数组中,每个元素都按这个有效位数来算,例如下面数组:

# Int数组
[8, 12, 100, 120]

# 各个元素与其二进制表示
8 -> 0001000
12 -> 0001100
100 -> 1100100
120 -> 1111000

最大数120的有效位数是7,所以数组中所有数据都可以使用7位来表示。假设这个数组有128个元素,且最大位120,那么这个压缩后的数组就如以下:
image.png
最终这个数组占用的位数位7bit * 128 = 896bit =112字节(这里没有算len占用的空间,但是不要忽略len也是要花费空间存储的),这比起原数据128 * 4字节=512字节确实节省了不少

PackedBlock只能存储固定长度的数组数据,如果一个Segment的文档数不是128的整数倍,那该怎么办那?这时可以使用VintBlock来进行存储

  • VintBlock

VintBlock通过使用变长整形编码方式来进行压缩,所以其可以存储复合的数据类型,不需要想PackedInts那样规定数据元素是多少位的。VintBlock采用Vint来对Int类型进行压缩,Vint采用可变长的字节来表示一个整数,每个字节只使用第1位到第7位来存储数据,第8位用来作为是否需要读取下一个字节的标记
image.png
如上图,本身Int 200需要4个字节,使用Vint只需要2个字节存储即可

  • 使用PackedBlock与VintBlock来解析.doc文件

Lucene在处理文档的时候,每处理 128 篇文档就会将其对应的文档 ID 数组(docDeltaBuffer)和词频(TermFreq)数组(freqBuffer)处理为两个 Block:并且使用 PackedInts 类对数据进行压缩存储,生成一个 PackedBlock。而把最后不足 128 篇文档的数据采用 VIntBlock 来存储(如果有 131 个文档,会生成一个 PackedBlock 和 3 个 VIntBlock)。并且在生成 PackedBlock 的时候,会生成跳表(SkipData),使得在读取数据时可以快速跳到指定的 PackedBlock。
下图为你整理了处理 .doc 文件的数据结构,以加深你对 Posting List 的理解。

image.png
  • 使用文档Id差值存储来节省空间

整数的压缩可以使当个数值占用的空间得以减少,但数与数之间还存在着微妙的关系,两个正整数的差一定会比它们种最大的那一个要小
利用这个特性,我们可以使需要压缩的整数变成最小的数,从而进一步压缩空间。因为存储文档Id的数组是有序的(需要注意的是,词频是无序的,无法使用下面的处理方式),所以在保存文档ID的时候,docDeltaBuffer 中存的不是文档的 ID,而是与上一个文档 ID 的差值,这样做可以进一步压缩存储空间!

ids = [1, 4, 6, 9, 14, 17, 21, 25]

如上面给定的数组 ids,如果用 PackedInts 的方式来对数据进行压缩的话,那么25(0001 1001)最少需要用 5 bit 来存储,所以整个数组需要 5 * 8 = 40 bit 来存储。如果数组中存储的是文档 ID 的差值,那么这个差值数组如下:

dtIds = [1, 3, 2, 3, 5, 3, 4, 4]

dtIds 数组中最大值为 5(0000 0101),其最少需要 3 bit 来表示,那么整个数组只需要 3 * 8 = 24 bit,相对于原来需要 40 bit 来存储数据,差值存储的方式可以有效降低存储的空间。探其究竟,其实是两个正整数间的差值会比它们间最大的那个要小,那么需要的有效位数就可能会减少了。
其实综合了上面所有的流程,无非就是文档 ID 使用增量编码,数据分块存储、数据按需分配存储空间,而这整个过程叫做 FOR(Frame Of Reference)

2.2.4.2 文档Id列表的交集求解

如果你需要检索朝代是唐代、诗人姓李、诗名中包含“明月”的诗,这时候系统会返回 3 条包含对应文档 ID 的列表,如下图:
image.png
如果直接循环3个数组求交集,并不是很高效,下面对其进行优化。

  • 位图

如果我们将列表的数据改造成位图会如何?假设有两个posting list A、B和它们生成的位图如下:

A = [2, 3, 5] => BitmapA = [0, 0, 1, 1, 0, 1]
B = [1, 2, 5] => BitmapB = [0, 1, 1, 0, 0, 1]

BitmapA AND BitmapB = [0, 0, 1, 0, 0, 1]

image.png
生成位图的方式其实很简单:Bitmap[A[i]] = 1,其他为 0 即可。这样我们可以将 A、B 两个位图对应的位置直接做 AND 位运算,由于位图的长度是固定的,所以两个位图的 AND 运算的复杂度也是固定的,并且由于 CPU 执行位运算效率非常高,所以在位图不是特别大的情况下,使用位图求解交集是非常高效的。

但是位图位图也有其致命的缺点,总结下来有这几点:

  1. 位图可能会消耗大量的空间。即使位图只需要1bit就可以表示相对应的元素是否存在,但是如果一个列表的有一个元素特别大,例如数组 [1, 65535],则需要 65535 bit 来表示。如果用 int32 类型的数组来表示的话,一个位图就需要 512M(2^32 bit = 2^29 Byte = 512M),如果有 N 个这样的列表,需要的存储空间为 N * 512M,这个空间开销是非常可怕的!
  2. 位图只适合数据稠密的场景
  3. 位图只适合存储简单整形类型的数据,对于复杂的对象类型无法处理,或者说复杂的类型本身就无法在CPU上直接使用AND这样的操作符

业界为了解决位图空间消耗大的问题,会使用一种压缩位图技术,也就是Roading Bitmap来代替简单的位图。Roadring Bitmap在业界应用广泛,下面来看看Roaring Bitmap的实现

  • Roaring Bitmaps

它会把一个 32 位的整数分成两个部分:高 16 位和低 16 位。然后将高 16 位作为一个数值存储到有序数组里,这个数组的每一个元素都是一个块。而低 16 位则存储到 2^16 的位图中去,将对应的位置设置为 1。这样每个块对应一个位图,如下图:
image.png
Roaring Bitmaps 通过拆分数据,形成按需分配的有序数组 + Bitmaps 的形式来节省空间。其中一个 Bitmaps 最多 2^16 个 bit,共消耗 8K。而由于每个块中存的是整数,每个数需要 2 个字节来存储即可,这样的整数共有 2^16 个,所以有序数组最多消耗 2 Byte * 2^16 = 128K 的存储空间。由于存储块的有序数组是按需分配的,所以 Roaring Bitmaps 的存储空间由数据量来决定,而 Bitmaps 的存储空间则是由最大的数来决定。举个例子就是,数组[0, 2^32 - 1],使用 Bitmaps 的话需要 512M 来存储,而 Roaring Bitmaps只需要 2 * (2 Byte + 8K)。
由于 Roaring Bitmaps 由有序数组加上 Bitmaps 构成,所以要确认一个数是否在 Roaring Bitmaps 中,需要通过两次查询才能得到结果。先以高 16 位在有序数组中进行二分查找,其复杂度为 O(log n);如果存在,则再拿低 16 位在对应的 Bitmaps 中查找,判断对应的位置是否为 1 即可,此时复杂度为 O(1)。所以,Roaring Bitmaps 可以做到节省空间的同时,还有着高效的检索能力
但是机智的你不知道有没有看出来,如果每个块的 Bitmaps 一直消耗 8K 的存储是不是超级浪费?如果这一个块中的数据很少,或者很稀疏,那还不如把 Bitmaps 做成数组呢。那到底用数组还是用 Bitmaps,阈值在哪里呢?当数据量达到 4096 的时候,我们发现 4096 * 16 bit(2 Byte) = 8K,所以当一个块的数据量小于 4096 的时候,可以使用 short 类型的有序数组存储数据,并且使用变长数组进一步节省空间。
image.png
业界除了使用 Roaring Bitmaps 来求交集外,还使用了跳表。

  • 使用跳表加速多个列表交集的求解

跳表是一种有序的数据结构,它通过在每个节点中维持多个指向其他节点的索引,从而达到快速访问的目的
image.png
如上图,有序链表不能像有序数组那样使用二分法进行快速访问,但可以对有序链表维护一层或者多层索引使其快速访问。
在求解两个有序列表 A 和 B 的交集时,可以使用归并排序的方法来遍历两个列表,将复杂度从 O(M * N) 降低到 O(M + N),这里的 M、N 分别为这两个列表的长度。
链表归并求交集的过程可以总结为以下三个步骤。

  1. 第一步:将指针 p1 和 p2 分别指向列表 A 和 B 的开头。
  2. 第二部:比较 p1 和 p2 指向的数据,会出现 3 种情况,如果内容相等,说明是公共元素,需要加入到结果集中;如果 p1 的内容小于 p2 的内容,p1 向后移;如果 p1 的内容大于 p2 的内容,p2 向后移。总结起来就是,谁小谁就往前走,相等就一起往前走,直到有一方结束
  3. 第三步:重复第二步,直到 p1 或者 p2 到达链表尾为止。

这里我们再结合以下示意图和例子来加深一下对归并流程的理解。指针 p1 和 p2 的原位置如下图所示,下面我们就来看看它们是怎么移动的。
image.png

  1. p2 的内容小于 p1 的内容(2 < 3),基于谁小谁就往前走的原则,所以 p2 往后移动一位,移动后的结果如下图:

image.png

  1. p1 的内容小于 p2 的内容(3 < 4),基于谁小谁就往后走的原则,所以 p1 往后移动一位,移动后的结果如下图:

image.png

  1. p2 的内容小于 p1 的内容(4 < 5),基于谁小谁就往后走的原则,所以 p2 往后移动一位,移动后的结果如下图:

image.png

  1. p2 的内容等于 p1 的内容(5 = 5),所以 5 为公共元素,需要记录下来,并且基于相等就一起往后走的原则,所以 p2 和 p1 都往后移动一位,移动后的结果如下图:

image.png
就这样,一直继续上述的流程直到链表B结束

归并排序的方式可以降低链表求交集的复杂度,但上述的算分还用优化的的地方。从下图中可以看出,此时 p1 要从 10 开始找直到多次才能找到 1000 这个大于或者等于 p2 的元素
image.png
要优化这个问题,可以使用前面介绍的跳表,给链表做索引。下图中,p1 使用跳表索引经过比较少的次数就可以找到 1000 了。
image.png

那既然链表 A 和 B 都有跳表索引的结构,那在链表 B 中查找数据也同样可以使用跳表索引的结构进行加速。如下图,当 p1 的值大于链表 B 当前值时,用 p1 的值当作 key,在链表 B 中使用二分查找,这样 p2 就可以使用跳表索引快速往前跳了。
image.png
这种二分查找法可以总结为:谁当前值大,就用其作为 key 到其他列表中做二分查找。但是,需要说明的是,虽然使用跳表索引可以降低查询的复杂度,但是跳表索引是要消耗空间的,并且在构建跳表的时候,也需要消耗额外的时间!

总结

ES 中的倒排索引主要分为 3 个部分:Term Index、Term Dictionary 和 Posting List。

  • Term Index 是 Term Dictionary 的索引,使用它可以快速判断一个 Term 是否存在,并且可以找到这个 Term 在 Term Dictionary 存储的块地址。Term Index 使用了 FST 来实现,其有着小巧且复杂度低的特点。
  • 而 Term Dictionary 是存储 Term 信息的地方,其内容包括 Term、包含该 Term 的文档的数量、Term 在整个 Segment 中出现的频率等。
  • 我们还学习了 Posting List 的实现,了解了 Lucene 使用 PackedBlock 和 VIntBlock 对整数进行压缩的思路,也介绍了业界使用 Roaring Bitmaps 和跳表来解决列表求交集的方案。

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

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

相关文章

lua学习笔记6(经典问题输出99乘法表)

print("************for循环的99乘法表*************") for i 1, 9 dolocal line "" -- 创建一个局部变量来累积每行的输出--local 是一个关键字&#xff0c;用于声明一个局部变量。for j 1, i doline line .. j .. "*" .. i .. ""…

mysql查询时大小写,末尾空格容易造成的问题。

问题描述 在使用mysql innodb 默认的配置时&#xff0c;发现mysql查询时是忽略大小写和末尾空格的。 示例 比如下面的查询&#xff1a;查询条件中是大写的字母&#xff0c;末尾也没有空格。但是查询出的结果中&#xff0c;既有小写的&#xff0c;也有末尾带有空格的&#xf…

Windows Edge浏览器的兼容性问题及解决方案

1、Windows Edge&#xff08;了解 Microsoft Edge&#xff09;&#xff1a; 简单介绍&#xff1a; Microsoft Edge是一款由微软开发的网页浏览器&#xff0c;最初于2015年伴随Windows 10推出&#xff0c;作为Internet Explorer的继任者&#xff0c;旨在提供更快、更安全、更现代…

二维相位解包理论算法和软件【全文翻译- 掩码(3.4)】

本节我们将研究从质量图中提取掩码的问题。掩码是一个质量图,其像素只有两个值:0 或 1。零值像素标志着质量最低的相位值,这些相位值将被屏蔽、零权重或忽略。第 5 章中的某些 L/ 正则算法需要使用掩码来定义零权重。掩码还可用于某些路径跟踪算法,如第 4.5 节中将要介绍的…

Linux:安装zabbix-agent被监控端(2)

本章是结合着上一篇文章的续作 Linux&#xff1a;部署搭建zabbix6&#xff08;1&#xff09;-CSDN博客https://blog.csdn.net/w14768855/article/details/137426966?spm1001.2014.3001.5501本章将在两台centos部署agent端&#xff0c;然后使用server进行连接监控 agent1 在1…

PostgreSQL技术内幕(十五):深度解析PG事务管理和分布式事务

PostgreSQL技术内幕&#xff08;十五&#xff09;&#xff1a;深度解析PG事务管理和分布式事务 事务作为保障数据完整性和一致性的关键机制&#xff0c;在数据库操作中扮演着举足轻重的角色。事务通过将一系列逻辑上的操作捆绑在一起&#xff0c;确保它们要么全部成功执行&…

IPD集成项目管理:继电器供应商宏发股份上线奥博思 PowerProject 项目管理系统平台

厦门宏发电声股份有限公司&#xff08;以下简称“宏发股份”&#xff09;签约北京奥博思软件公司&#xff0c;选择PowerProject建设全生命周期数字化项目管理平台。 了解奥博思项目管理软件&#xff1a;http://www.powerproject.com.cn/ 宏发股份项目管理系统启动会顺利召开 …

rsync-远程同步备份

目录 前言 一、rsync远程同步概述 1、rsync远程同步概念 2、rsync功能及特点 3、rsync的备份方式 4、rsync的上下行同步方式 4.1 下行同步 4.2 上行同步 5、rsyncinotify 6、rsync同类服务 7、rsync与cp、scp对比 二、rsync命令 1、rsync格式语法 2、rsync配置同…

Clarity AI:免费开源的AI无损图片放大图像升级器和增强工具

可以作为Magnific AI的平替版本。Magnific AI是一款基于人工智能技术的图像处理工具&#xff0c;主要功能包括图像放大、像素级AI重绘、灵活的设置调整以及多种优化场景。它能够支持最高放大至16倍&#xff0c;甚至可以达到1亿像素的分辨率。此外&#xff0c;Magnific AI还具备…

MySQL中数据库、表的操作

文章目录 一、管理数据库1.1、连接数据库1.2、创建库1.3、选择数据库1.4、修改数据库名称1.5、查看数据库信息1.6、删除库 二、定义数据表字段2.1、数据表字段的数据类型2.2、数据表字段属性2.3、约束讲解2.3.1、约束的定义1&#xff09;为什么需要约束2&#xff09;什么是约束…

敏感信息泄露漏洞

法律声明 参与培训需要遵守国家法律法规&#xff0c;相关知识只做技术研究&#xff0c;请勿用于违法用途&#xff0c;造成任何后果自负与本人无关。 中华人民共和国网络安全法&#xff08;2017年6月1日起施行&#xff09; 第二十二条 任何个人和组织不得从事入侵他人网络、干扰…

CAD Plant3D 2024 下载地址及安装教程

CAD Plant3D是一款专业的三维工厂设计软件&#xff0c;用于在工业设备和管道设计领域进行建模和绘图。它是Autodesk公司旗下的AutoCAD系列产品之一&#xff0c;专门针对工艺、石油、化工、电力等行业的设计和工程项目。 CAD Plant3D提供了一套丰富的工具和功能&#xff0c;帮助…

八次危机笔记

文章目录 前言一、思维导图危机一危机二危机三危机四危机五危机六危机七危机八 前言 重塑三观&#xff0c;致敬温老。一个有良心的学者&#xff01;&#xff01;&#xff01; 一、思维导图 危机一 危机二 危机三 危机四 危机五 危机六 危机七 危机八 ☆

如何实现异地公网环境访问本地部署的支付宝沙箱环境调试支付SDK

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

qt 打印日志

在 Qt Creator 中&#xff0c;将 QDebug、QInfo、QWarning、QCritical 和 QFatal 打印的日志输出到指定文件&#xff0c;需要设置 Qt 的消息处理机制。这通常涉及到安装一个自定义的消息处理器&#xff0c;该处理器将日志消息重定向到文件。以下是一个基本的步骤指南&#xff1…

鸿蒙实现一种仿小红书首页滑动联动效果

前言&#xff1a; DevEco Studio版本&#xff1a;4.0.0.600 效果描述&#xff1a;通过手指滑动列表&#xff0c;控制位置显示效果为&#xff1a;不论列表在什么位置下滑时下图粉色位置布局显示&#xff0c;手指上滑时下图粉色位置布局隐藏。 效果&#xff1a; 原理分析&…

【分治算法】大整数乘法Python实现

文章目录 [toc]问题描述基础算法时间复杂性 优化算法时间复杂性 Python实现 个人主页&#xff1a;丷从心. 系列专栏&#xff1a;Python基础 学习指南&#xff1a;Python学习指南 问题描述 设 X X X和 Y Y Y都是 n n n位二进制整数&#xff0c;计算它们的乘积 X Y XY XY 基础…

System Verilog中的线程fork...join_none的#0时延

System Verilog 首先对fork…join_none里的线程进行调度&#xff0c;但是由于#0时延阻塞了当前线程&#xff0c;并且将它重新调度到当前时间片之后启动时延使得当前线程必须等到所有在fork…join_none语句中产生的线程执行完之后才得以运行。 program no_auto;initial beginfo…

二叉树练习day.3

104.二叉树的最大深度 链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root…

使用单点登录(SSO)如何提高安全性和用户体验

什么是单点登录&#xff08;SSO&#xff09; 对于所有大量采用云应用程序的组织来说&#xff0c;有效的身份管理是一个巨大的挑战&#xff0c;如果每个 SaaS 应用程序的用户身份都是独立管理的&#xff0c;则用户必须记住多个密码&#xff0c;技术支持技术人员在混合环境中管理…