通过对象属性去重_Redis常见对象类型的底层数据结构

作者:伍陆七

来源:cnblogs.com/chentianming/p/13838347.html

Redis 是一个基于内存中的数据结构存储系统,可以用作数据库、缓存和消息中间件。Redis 支持五种常见对象类型:字符串(String)、哈希(Hash)、列表(List)、集合(Set)以及有序集合(Zset),我们在日常工作中也会经常使用它们。知其然,更要知其所以然,本文将会带你读懂这五种常见对象类型的底层数据结构。

本文主要内容参考自《Redis设计与实现》

1. 对象类型和编码

Redis 使用对象来存储键和值的,在Redis中,每个对象都由 redisObject 结构表示。redisObject 结构主要包含三个属性:type、encoding 和 ptr。

typedef struct redisObject {    // 类型    unsigned type:4;    // 编码    unsigned encoding:4;    // 底层数据结构的指针    void *ptr;} robj;

其中 type 属性记录了对象的类型。对于 Redis 来说,键对象总是字符串类型,值对象可以是任意支持的类型。因此,当我们说 Redis 键采用哪种对象类型的时候,指的是对应的值采用哪种对象类型。

706fa2c4797db508bc0a4e3848e2fb46.png

*ptr 属性指向了对象的底层数据结构,而这些数据结构由 encoding 属性决定。

95e7f41baae8f28d3c3711848c93ae70.png

之所以由 encoding 属性来决定对象的底层数据结构,是为了实现同一对象类型,支持不同的底层实现。这样就能在不同场景下,使用不同的底层数据结构,进而极大提升Redis的灵活性和效率。

底层数据结构后面会详细讲解,这里简单看一下即可。

2. 字符串对象

字符串是我们日常工作中用得最多的对象类型,它对应的编码可以是 int、raw 和 embstr。字符串对象相关命令可参考:Redis命令-Strings。

如果一个字符串对象保存的是不超过 long 类型的整数值,此时编码类型即为 int,其底层数据结构直接就是 long 类型。例如执行 set number 10086,就会创建 int 编码的字符串对象作为 number 键的值。

bd55bacdc62f67cc2c01a4243f4a4837.png

如果字符串对象保存的是一个长度大于 39 字节的字符串,此时编码类型即为 raw,其底层数据结构是简单动态字符串(SDS);如果长度小于等于 39 个字节,编码类型则为 embstr,底层数据结构就是 embstr 编码 SDS。下面,我们详细理解下什么是简单动态字符串。

2.1 简单动态字符串

SDS 定义

在 Redis 中,使用 sdshdr 数据结构表示 SDS:

struct sdshdr {    // 字符串长度    int len;    // buf数组中未使用的字节数    int free;    // 字节数组,用于保存字符串    char buf[];};

SDS 遵循了 C 字符串以空字符结尾的惯例,保存空字符的 1 字节不会计算在 len 属性里面。例如,Redis 这个字符串在 SDS 里面的数据可能是如下形式:

72013d2346ba9c0deca320512b4000ff.pngSDS 与 C 字符串的区别

C 语言使用长度为 N+1 的字符数组来表示长度为N的字符串,并且字符串的最后一个元素是空字符 \0。Redis 采用 SDS 相对于 C 字符串有如下几个优势:

  1. 常数复杂度获取字符串长度;

  2. 杜绝缓冲区溢出;

  3. 减少修改字符串时带来的内存重分配次数;

  4. 二进制安全。

常数复杂度获取字符串长度

因为 C 字符串并不记录自身的长度信息,所以为了获取字符串的长度,必须遍历整个字符串,时间复杂度是 O(N)。而 SDS 使用 len 属性记录了字符串的长度,因此获取 SDS字符串长度的时间复杂度是 O(1)

杜绝缓冲区溢出

C 字符串不记录自身长度带来的另一个问题是,很容易造成缓存区溢出。比如使用字符串拼接函数(stract)的时候,很容易覆盖掉字符数组原有的数据。

与 C 字符串不同,SDS 的空间分配策略完全杜绝了发生缓存区溢出的可能性。当 SDS 进行字符串扩充时,首先会检查当前的字节数组的长度是否足够。如果不够的话,会先进行自动扩容,然后再进行字符串操作。

减少修改字符串时带来的内存重分配次数

因为 C 字符串的长度和底层数据是紧密关联的,所以每次增长或者缩短一个字符串,程序都要对这个数组进行一次内存重分配:

  • 如果是增长字符串操作,需要先通过内存重分配来扩展底层数组空间大小,不这么做就导致缓存区溢出;

  • 如果是缩短字符串操作,需要先通过内存重分配来来回收不再使用的空间,不这么做就导致内存泄漏。

因为内存重分配涉及复杂的算法,并且可能需要执行系统调用,所以通常是个比较耗时的操作。对于 Redis 来说,字符串修改是一个十分频繁的操作。如果每次都像 C 字符串那样进行内存重分配,对性能影响太大了,显然是无法接受的。

SDS 通过空闲空间解除了字符串长度和底层数据之间的关联。在 SDS 中,数组中可以包含未使用的字节,这些字节数量由 free 属性记录。通过空闲空间,SDS 实现了空间预分配和惰性空间释放两种优化策略

1. 空间预分配

空间预分配是用于优化 SDS 字符串增长操作的,简单来说就是当字节数组空间不足触发重分配的时候,总是会预留一部分空闲空间。这样的话,就能减少连续执行字符串增长操作时的内存重分配次数。

有两种预分配的策略:

  • len 小于 1MB 时:每次重分配时会多分配同样大小的空闲空间;

  • len 大于等于 1MB 时:每次重分配时会多分配 1MB 大小的空闲空间。

2. 惰性空间释放

惰性空间释放是用于优化 SDS 字符串缩短操作的。简单来说就是当字符串缩短时,并不立即使用内存重分配来回收多出来的字节,而是用 free 属性记录,等待将来使用。SDS 也提供直接释放未使用空间的 API,在需要的时候,也能真正的释放掉多余的空间。

二进制安全

C 字符串中的字符必须符合某种编码,并且除了字符串末尾之外,其它位置不允许出现空字符。这些限制使得 C 字符串只能保存文本数据。

但是对于 Redis 来说,不仅仅需要保存文本,还要支持保存二进制数据。为了实现这一目标,SDS 的 API 全部做到了二进制安全(binary-safe)。

2.2 raw 和 embstr 编码的 SDS 区别

我们在前面讲过,长度大于 39 字节的字符串,编码类型为 raw,底层数据结构是简单动态字符串(SDS)。这个很好理解,比如当我们执行 set story "Long, long, long ago there lived a king ..."(长度大于39)之后,Redis 就会创建一个 raw 编码的 String 对象。

数据结构如下:

4bdb4e49a6953a5d312932e05e968e38.png

长度小于等于 39 个字节的字符串,编码类型为 embstr,底层数据结构则是 embstr 编码 SDS。embstr 编码是专门用来保存短字符串的,它和 raw 编码最大的不同在于:raw 编码会调用两次内存分配分别创建 redisObject 结构和 sdshdr 结构;而 embstr 编码则是只调用一次内存分配,在一块连续的空间上同时包含 redisObject 结构和 sdshdr 结构。

acbb3bf85919fb0693b9765ec6887ec3.png

2.3 编码转换

int 编码和 embstr 编码的字符串对象在条件满足的情况下会自动转换为 raw 编码的字符串对象。

对于 int 编码来说,当我们修改这个字符串为不再是整数值的时候,此时字符串对象的编码就会从 int 变为 raw。

对于 embstr 编码来说,只要我们修改了字符串的值,此时字符串对象的编码就会从 embstr 变为 raw。

embstr 编码的字符串对象可以认为是只读的,因为 Redis 为其编写任何修改程序。当我们要修改 embstr 编码字符串时,都是先将转换为 raw 编码,然后再进行修改。

3. 列表对象

列表对象的编码可以是 linkedlist 或者 ziplist,对应的底层数据结构是链表和压缩列表。列表对象相关命令可参考:Redis 命令-List。

默认情况下,当列表对象保存的所有字符串元素的长度都小于 64 字节,且元素个数小于 512 个时,列表对象采用的是 ziplist 编码,否则使用 linkedlist 编码。

可以通过配置文件修改该上限值。

3.1 链表

链表是一种非常常见的数据结构,提供了高效的节点重排能力以及顺序性的节点访问方式。在 Redis 中,每个链表节点使用 listNode 结构表示:

typedef struct listNode {    // 前置节点    struct listNode *prev;    // 后置节点    struct listNode *next;    // 节点值    void *value;} listNode

多个 listNode 通过 prev 和 next 指针组成双端链表,如下图所示:

dd3ffe770589052f7cc9397bfede90b3.png

为了操作起来比较方便,Redis 使用了 list 结构持有链表。

typedef struct list {    // 表头节点    listNode *head;    // 表尾节点    listNode *tail;    // 链表包含的节点数量    unsigned long len;    // 节点复制函数    void *(*dup)(void *ptr);    // 节点释放函数    void (*free)(void *ptr);    // 节点对比函数    int (*match)(void *ptr, void *key);} list;

list 结构为链表提供了表头指针 head、表尾指针 tail,以及链表长度计数器 len,而 dup、free 和 match 成员则是实现多态链表所需类型的特定函数。

8fad50b4ce0ff14464823cfc76a1e194.png

Redis 链表实现的特征总结如下:

  1. 双端:链表节点带有 prev 和 next 指针,获取某个节点的前置节点和后置节点的复杂度都是 O(n)

  2. 无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问以 NULL 为终点;

  3. 带表头指针和表尾指针:通过 list 结构的 head 指针和 tail 指针,程序获取链表的表头节点和表尾节点的复杂度为 O(1)

  4. 带链表长度计数器:程序使用 list 结构的 len 属性来对 list 持有的节点进行计数,程序获取链表中节点数量的复杂度为 O(1)

  5. 多态:链表节点使用 void* 指针来保存节点值,可以保存各种不同类型的值。

3.2 压缩列表

压缩列表(ziplist)是列表键和哈希键的底层实现之一。压缩列表主要目的是为了节约内存,是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。

ab4aa1c7f55bffe0c2d39c2ab6ed009e.png

如上图所示,压缩列表记录了各组成部分的类型、长度以及用途。

ce875328d8c6159d6580c5393d812143.png

4. 哈希对象

哈希对象的编码可以是 ziplist 或者 hashtable。

4.1 hash-ziplist

ziplist 底层使用的是压缩列表实现,上文已经详细介绍了压缩列表的实现原理。每当有新的键值对要加入哈希对象时,先把保存了键的节点推入压缩列表表尾,然后再将保存了值的节点推入压缩列表表尾。比如,我们执行如下三条 HSET 命令:

HSET profile name "tom"HSET profile age 25HSET profile career "Programmer"

如果此时使用 ziplist 编码,那么该 Hash 对象在内存中的结构如下:

4982dc3c9eea4c6a2f7ab6e9c89e9307.png

3.2 hash-hashtable

hashtable 编码的哈希对象使用字典作为底层实现。字典是一种用于保存键值对的数据结构,Redis 的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,每个哈希表节点保存的就是一个键值对。

3.3 哈希表

Redis 使用的哈希表由 dictht 结构定义:

typedef struct dictht{    // 哈希表数组    dictEntry **table;    // 哈希表大小    unsigned long size;    // 哈希表大小掩码,用于计算索引值    // 总是等于 size-1    unsigned long sizemask;    // 该哈希表已有节点数量    unsigned long used;} dictht

table 属性是一个数组,数组中的每个元素都是一个指向 dictEntry 结构的指针,每个 dictEntry 结构保存着一个键值对。

size 属性记录了哈希表的大小,即 table 数组的大小。used 属性记录了哈希表目前已有节点数量。sizemask 总是等于 size-1,这个值主要用于数组索引。

比如下图展示了一个大小为 4 的空哈希表。

67d98b15a45f14279f30cbb42c59a219.png

哈希表节点

哈希表节点使用 dictEntry 结构表示,每个 dictEntry 结构都保存着一个键值对:

typedef struct dictEntry {    // 键    void *key;    // 值    union {        void *val;        unit64_t u64;        nit64_t s64;    } v;    // 指向下一个哈希表节点,形成链表    struct dictEntry *next;} dictEntry;

key 属性保存着键值对中的键,而 v 属性则保存了键值对中的值。值可以是一个指针,一个 uint64_t 整数或者是 int64_t 整数。next 属性指向了另一个 dictEntry 节点,在数组桶位相同的情况下,将多个 dictEntry 节点串联成一个链表,以此来解决键冲突问题(链地址法)。

3.4 字典

Redis 字典由 dict 结构表示:

typedef struct dict {    // 类型特定函数    dictType *type;    // 私有数据    void *privdata;    // 哈希表    dictht ht[2];    //rehash索引    // 当rehash不在进行时,值为-1    int rehashidx;}

ht 是大小为 2,且每个元素都指向 dictht 哈希表。一般情况下,字典只会使用 ht[0] 哈希表,ht[1] 哈希表只会在对 ht[0] 哈希表进行 rehash 时使用。rehashidx 记录了 rehash 的进度,如果目前没有进行 rehash,值为 -1。

rehash

为了使 hash 表的负载因子 (ht[0]).used/ht[0]).size) 维持在一个合理范围,当哈希表保存的元素过多或者过少时,程序需要对 hash 表进行相应的扩展和收缩。

rehash(重新散列)操作就是用来完成 hash 表的扩展和收缩的。

rehash 的步骤如下:

1. 为 ht [1] 哈希表分配空间;

  • 如果是扩展操作,那么 ht[1] 的大小为第一个大于 ht[0].used*2的2n。比如ht[0].used=5,那么此时 ht[1] 的大小就为 16(大于 10 的第一个 2n 的值是 16);

  • 如果是收缩操作,那么 ht[1] 的大小为第一个大于 ht[0].used 的 2n。比如ht[0].used=5,那么此时 ht[1] 的大小就为 8(大于 5 的第一个 2n 的值是 8)。

2. 将保存在 ht[0] 中的所有键值对 rehash 到 ht[1] 中;

3. 迁移完成之后,释放掉 ht[0],并将现在的 ht[1] 设置为 ht[0],在 ht[1] 新创建一个空白哈希表,为下一次 rehash 做准备。

哈希表的扩展和收缩时机

  • 当服务器没有执行 BGSAVE 或者 BGREWRITEAOF 命令时,负载因子大于等于 1 触发哈希表的扩展操作;

  • 当服务器在执行 BGSAVE 或者 BGREWRITEAOF 命令,负载因子大于等于 5 触发哈希表的扩展操作;

  • 当哈希表负载因子小于 0.1,触发哈希表的收缩操作。

渐进式 rehash

前面讲过,扩展或者收缩需要将 ht[0] 里面的元素全部 rehash 到 ht[1] 中,如果 ht[0] 元素很多,显然一次性 rehash 成本会很大,从影响到 Redis 性能。

为了解决上述问题,Redis 使用了渐进式 rehash 技术,具体来说就是分多次,渐进式地将 ht[0] 里面的元素慢慢地 rehash 到 ht[1] 中。

下面是渐进式 rehash 的详细步骤:

  1. 为 ht[1] 分配空间;

  2. 在字典中维持一个索引计数器变量 rehashidx,并将它的值设置为 0,表示 rehash 正式开始;

  3. 在 rehash 进行期间,每次对字典执行添加、删除、查找或者更新时,除了会执行相应的操作之外,还会顺带将 ht[0] 在 rehashidx 索引位上的所有键值对 rehash 到 ht[1] 中,rehash 完成之后,rehashidx 值加 1;

  4. 随着字典操作的不断进行,最终会在啊某个时刻迁移完成,此时将 rehashidx 值置为 -1,表示 rehash 结束。

渐进式 rehash 一次迁移一个桶上所有的数据。设计上采用分而治之的思想,将原本集中式的操作分散到每个添加、删除、查找和更新操作上,从而避免集中式 rehash 带来的庞大计算。

因为在渐进式 rehash 时,字典会同时使用 ht[0] 和 ht[1] 两张表,所以此时对字典的删除、查找和更新操作都可能会在两个哈希表进行。比如,如果要查找某个键时,先在 ht[0] 中查找,如果没找到,则继续到 ht[1] 中查找。

hash 对象中的 hashtable

HSET profile name "tom"HSET profile age 25HSET profile career "Programmer"

还是上述三条命令,保存数据到 Redis 的哈希对象中,如果采用 hashtable 编码保存的话,那么该 Hash 对象在内存中的结构如下:

b7e0aea29599d479bb5d1cafe7cb1f29.png

当哈希对象保存的所有键值对的键和值的字符串长度都小于 64 个字节,并且数量小于 512 个时,使用 ziplist 编码,否则使用 hashtable 编码。

可以通过配置文件修改该上限值。

4. 集合对象

集合对象的编码可以是 intset 或者 hashtable。当集合对象保存的元素都是整数,并且个数不超过 512 个时,使用 intset 编码,否则使用 hashtable 编码。

4.1 set-intset

intset 编码的集合对象底层使用整数集合实现。

整数集合(intset)是 Redis 用于保存整数值的集合抽象数据结构,它可以保存类型为 int16_t、int32_t 或者 int64_t 的整数值,并且保证集合中的数据不会重复。Redis 使用 intset 结构表示一个整数集合。

typedef struct intset {    // 编码方式    uint32_t encoding;    // 集合包含的元素数量    uint32_t length;    // 保存元素的数组    int8_t contents[];} intset;

contents 数组是整数集合的底层实现:整数集合的每个元素都是 contents 数组的一个数组项,各个项在数组中按值大小从小到大有序排列,并且数组中不包含重复项。

虽然 contents 属性声明为 int8_t 类型的数组,但实际上,contents 数组不保存任何 int8_t 类型的值,数组中真正保存的值类型取决于 encoding。

如果 encoding 属性值为 INTSET_ENC_INT16,那么 contents 数组就是 int16_t 类型的数组,以此类推。

当新插入元素的类型比整数集合现有类型元素的类型大时,整数集合必须先升级,然后才能将新元素添加进来。这个过程分以下三步进行:

  1. 根据新元素类型,扩展整数集合底层数组空间大小;

  2. 将底层数组现有所有元素都转换为与新元素相同的类型,并且维持底层数组的有序性;

  3. 将新元素添加到底层数组里面。

还有一点需要注意的是,整数集合不支持降级。一旦对数组进行了升级,编码就会一直保持升级后的状态。

举个例子,当执行 SADD numbers 1 3 5 向集合对象插入数据时,该集合对象在内存的结构如下:

0ee8490abec6e20d34bdc8fe3e904026.png

4.2 set-hashtable

hashtable 编码的集合对象使用字典作为底层实现。字典的每个键都是一个字符串对象,每个字符串对象对应一个集合元素,字典的值都是 NULL。

当我们执行 SADD fruits "apple" "banana" "cherry" 向集合对象插入数据时,该集合对象在内存的结构如下:

9ec30c3e27d384114aff680a568acbf7.png

5. 有序集合对象

有序集合的编码可以是 ziplist 或者 skiplist。当有序集合保存的元素个数小于 128 个,且所有元素成员长度都小于 64 字节时,使用 ziplist 编码,否则使用 skiplist 编码。

5.1 zset-ziplist

ziplist 编码的有序集合使用压缩列表作为底层实现。每个集合元素使用两个紧挨着一起的两个压缩列表节点表示,第一个节点保存元素的成员(member),第二个节点保存元素的分值(score)。

压缩列表内的集合元素按照分值从小到大排列。如果我们执行 ZADD price 8.5 apple 5.0 banana 6.0 cherry 命令向有序集合插入元素,该有序集合在内存中的结构如下:

523295bd93105951877b6363678065dc.png

5.2 zset-skiplist

skiplist 编码的有序集合对象使用 zset 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表。

typedef struct zset {    zskiplist *zs1;    dict *dict;}

继续介绍之前,我们先了解一下什么是跳跃表。

跳跃表

跳跃表(skiplist)是一种有序的数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。

Redis 的跳跃表由 zskiplistNode 和 zskiplist 两个结构定义。zskiplistNode 结构表示跳跃表节点,zskiplist 保存跳跃表节点相关信息,比如节点的数量,以及指向表头和表尾节点的指针等。

跳跃表节点 zskiplistNode

跳跃表节点 zskiplistNode 结构定义如下:

typedef struct zskiplistNode {    // 后退指针    struct zskiplistNode *backward;    // 分值    double score;    // 成员对象    robj *obj;    // 层    struct zskiplistLevel {        // 前进指针        struct zskiplistNode *forward;        // 跨度        unsigned int span;    } level[];} zskiplistNode;

下图是一个层高为 5,包含 4 个跳跃表节点(1 个表头节点和 3 个数据节点)组成的跳跃表:

9a155420de902f5b1912511f07595525.png

有序集合对象的 skiplist 实现

前面讲过,skiplist 编码的有序集合对象使用 zset 结构作为底层实现。一个 zset 结构同时包含一个字典和一个跳跃表。

typedef struct zset {    zskiplist *zs1;    dict *dict;}

zset 结构中的 zs1 跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素。

通过跳跃表,可以对有序集合进行基于 score 的快速范围查找。zset 结构中的 dict 字典为有序集合创建了从成员到分值的映射,字典的键保存了成员,字典的值保存了分值。通过字典,可以用 O(1) 复杂度查找给定成员的分值。

假如还是执行 ZADD price 8.5 apple 5.0 banana 6.0 cherry 命令向 zset 保存数据,如果采用 skiplist 编码方式的话,该有序集合在内存中的结构如下:

47f71dbef4650768c71c6e4f46d6f3d9.png

6. 总结

总的来说,Redis 底层数据结构主要包括简单动态字符串(SDS)、链表、字典、跳跃表、整数集合和压缩列表六种类型。并且基于这些基础数据结构实现了字符串对象、列表对象、哈希对象、集合对象以及有序集合对象五种常见的对象类型。每一种对象类型都至少采用了 2 种数据编码,不同的编码使用的底层数据结构也不同。

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。祝愿每一位读者生活愉快!谢谢!

b53809d6f74fd170543e3b9548cb09bd.png

长按我每天进步一点点

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

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

相关文章

按照演算,整个宇宙将会陷入无边的黑暗

导读:能量守恒定律告诉我们:能量既不会凭空产生,也不会凭空消失,它只会从一种形式转化为另一种形式,或者从一个物体转移到其它物体,而能量的总量保持不变。熵作为只增不减的物质,该怎么去理解它…

xp计算机启动检测硬盘,取消WinXP开机自检技巧五则

有时我们正常关闭计算机后,再次开机时发现系统会出现自行检测,这让许多XP用户们感到不方便,那么该怎么取消XP开机自检呢?下面就是具体的方法了,一起来看看吧。方法①:假如分区是FAT32格式,将其转…

10 邮件槽_员工主动发离职邮件,提出申请又反悔,法院判决让人懵了!

前言:很多职场人从来不把劳动法当作一项技能,一遇到事,瞬间就傻。还有部分职场人,什么事都不做,只会说劳动法没有用。就笔者认识的一部分大厂员工,他们现在已经把每天视频打卡跟录音取证作为一项日常工作来…

干货|机器学习零基础?不要怕,吴恩达机器学习课程笔记2-多元线性回归

吴恩达Coursera机器学习课系列笔记课程笔记|吴恩达Coursera机器学习 Week1 笔记-机器学习基础1Linear Regression with Multiple Variables紧接上一篇的例子 – 房价预测。现在我们有更多的特征来预测房价了,“房间的数量”、“楼层”、“房龄”……说明一下接下来要…

技能高考本科计算机类,技能高考多少分上本科

技能高考总分为700分,包括专业技能满分490分,文化课满分210分。能上大学只要总分300往上都可以。如果是本科的话,每个专业的分数线不一样,少的在400分左右,多的比如会计类专业的话可能要到500以上才能报考本科院校。什…

新增一个主键自增长_MyBatis 示例-主键回填

测试类&#xff1a;com.yjw.demo.PrimaryKeyTest自增长列数据库表的主键为自增长列&#xff0c;在写业务代码的时候&#xff0c;经常需要在表中新增一条数据后&#xff0c;能获得这条数据的主键 ID&#xff0c;MyBatis 提供了实现的方法。StudentMapper.xml<insert id"…

干货|机器学习零基础?不要怕,吴恩达课程笔记第三周!逻辑回归与正则

吴恩达Coursera机器学习课系列笔记课程笔记|吴恩达Coursera机器学习 Week1 笔记-机器学习基础干货|机器学习零基础&#xff1f;不要怕&#xff0c;吴恩达机器学习课程笔记2-多元线性回归1Logistic Regression1.1 Logistic Regression (Classification) Model之前对房价的预测&a…

计算机网络互联网技术实验报告,2013计算机网络技术与应用.实验报告01

本报告 6 月 5 日前完成。 此框阅读后删除。 此处填写&#xff1a;年级和姓名。 《计算机网络技术与应用》实验报告 此框阅读后删除。 年级、专业、班级 实验题目 实验时间 实验成绩 2013.4.1 11 级 专业 班 姓名计算机网络应用软件与拓扑结构实验地点 实验性质 DS1422■验证性…

如何在C#中使用 ArrayPool,MemoryPool

对资源的可复用是提升应用程序性能的一个非常重要的手段&#xff0c;比如本篇要分享的 ArrayPool 和 MemoryPool&#xff0c;它们就有效的减少了内存使用和对GC的压力&#xff0c;从而提升应用程序性能。什么是 ArrayPool System.Buffers 命名空间下提供了一个可对 array 进行复…

LAMP攻略: LAMP环境搭建,Linux下Apache,MySQL,PHP安装与配置

之前写过一个red hat 9下的LAMP环境的配置&#xff0c;不过由于版本比较旧&#xff0c;很多不适用了。 所以决定写一个新的LAMP环境搭建与配置教程。本配置是在 CentOS-5.3 下 httpd-2.2.11.tar.gz MySQL-client-community-5.1.33-0.rhel5.i386.rpm MySQL-devel-community-5.1…

服务器自动删文件,服务器定时删除文件工具

服务器定时删除文件工具&#xff0c;这是一个定时删除服务器上文件的小程序修改配置文件config.ini&#xff0c;dir是主目录;dirs是要删除文件目录;deltype是删除类型,0是创建日期,1是修改日期;delday是保留天数;deltime是定时删除时间。[config]dir\\cb19\pictifdirs01,02,03,…

30 个实例详解 TOP 命令

Linux中的top命令显示系统上正在运行的进程。它是系统管理员最重要的工具之一。被广泛用于监视服务器的负载。在本篇中&#xff0c;我们会探索top命令的细节。top命令是一个交互命令。在运行top的时候还可以运行很多命令。我们也会探索这些命令。&#xff08;译注&#xff1a;不…

IComparer与IEqualityComparer的简单使用

场景一&#xff1a;对象列表的自定义排序简单类型的列表&#xff0c;可以直接使用Linq的OrderBy或OrderByDescending进行排序&#xff0c;复杂对象的列表排序可以使用Sort()和IComparer实现自定义对象比较规则。假如有一个Box类&#xff0c;它有名称、长、宽、高四个属性&#…

程序显示文本框_【教程】TestComplete测试桌面应用程序教程(二)

TestComplete是一款具有人工智能的自动UI测试工具&#xff0c;利用自动化测试工具和人工智能支持的混合对象识别引擎&#xff0c;轻松检测和测试每个桌面&#xff0c;Web和移动应用程序。其中&#xff0c;TestComplete支持测试使用C、C&#xff03;、VB.NET、Java、Delphi、C …

陕西省计算机二级报名流程,计算机二级考试报名流程

计算机二级考试报名流程第一次参加全国计算机等级考试的考生对于网上报名的流程&#xff0c;对全国计算机考试流程中某些环节并不清楚。下面是小编为大家带来的计算机二级考试报名流程&#xff0c;欢迎阅读。(一)注册账号和登录1)考生首次登录系统需要注册登录通行证&#xff0…

Git 的 4 个阶段的撤销更改

虽然git诞生距今已有12年之久&#xff0c;网上各种关于git的介绍文章数不胜数&#xff0c;但是依然有很多人&#xff08;包括我自己在内&#xff09;对于它的功能不能完全掌握。以下的介绍只是基于我个人对于git的理解&#xff0c;并且可能生编硬造了一些不完全符合git说法的词…

51CTO下载专题有奖征集建议:您的期待,我们的方向!

2010年5月10日&#xff0c;51CTO下载专题 隆重上线。精细的技术领域、优质的技术资源、大方的设计风格......让您对精品资源一网打尽&#xff0c;直达心灵所需&#xff01; 51CTO下载专题每周发布1-2期&#xff0c;旨在帮助大家在最短的时间里&#xff0c;找到自己感兴趣技术点…

Visual Studio SnippetDesigner使用

SnippetDesigner代码片段编辑器这是一款在Visual Studio上代码片段编辑器插件&#xff0c;可以轻松创建代码片段&#xff0c;为什么要用这个代码片段呢&#xff0c;理由&#xff1a;平常在编码过程中&#xff0c;有许多重复性的代码语句&#xff0c;为了提高编码速度与便捷&…

生态合作与自主可控

生态合作与自主可控是一个大题目&#xff0c;从不同角度来讨论&#xff0c;不同的人会得出不同的结论。为了交流不空洞&#xff0c;先介绍一个案例&#xff0c;它具有普遍性&#xff1a;L总应该是前年与我们联系过&#xff0c;今年刚刚与我们联系描述了一下他们的现状&#xff…

css规则中区块block,CSS的命名方式:BEM(区块、元素、修饰符)

原标题&#xff1a;CSS的命名方式:BEM(区块、元素、修饰符)本资源由 伯乐在线- 凝枫整理&#xff0c;您也想贡献一份力量&#xff1f;欢迎加入我们 重要概念“Block”区块区块的定义是&#xff1a;一个逻辑和功能兼备的独立页面组件&#xff0c;也可以称为web组件。一个区块包含…