当前位置: 首页 > news >正文

Redis学习笔记(二)Redis基础(基于5.0.5版本)

一、Redis定位与特性

Redis是一个速度非常快的非关系数据库(non-relational database),用 Key-Value 的形式来存储数据。数据主要存储在内存中,所以Redis的速度非常快,另外Redis也可以将内存中的数据持久化到硬盘上。
Redis主要特性:
1、速度快
Redis数据存放在内存中,读取速度非常快。
2、单线程
Redis使用单线程架构,避免了多线程可能产生的竞争开销
3、基于K-V的数据结构
Redis使用 Key-Value 的形式来存储数据
4、功能相对丰富
支持RDB和AOF两种持久化机制
支持多种键过期策略
支持Lua脚本
支持简单事务
支持发布订阅模式
5、高可用和分布式
Redis从2.8版本正式提供了高可用实现哨兵模式,可以保证Redis节点的故障发现和故障自动转移,
Redis从3.0版本后开始支持集群模式
支持主从复制
6、支持多种编程语言
Redis提供了简单的TCP通信协议,这样使得很多编程语言可以很方便的接入Redis

Redis基本操作命令:

#存值
set testkey testvalue
#取值
get testkey
#查看所有键
keys *
#获取键总数
dbsize
#查看键是否存在
exists testKey
#删除键
del testKey1 testKey2
#重命名键
rename oldkey newkey
#查看类型
type testkey

两个Redis命令学习网站
Redis 命令参考:http://redisdoc.com/index.html
Redis中文网站:http://www.redis.cn

二、Redis数据结构

Redis常用的基本数据结构有五种,分别是String、List、Hash、Set、Zset。其他数据类型还有Hyperloglog、Geo、Streams。
Redis的五种常用的数据类型底层结构如下图所示。
redis底层原理图:
在这里插入图片描述
Redis是基于K-V的,它是通过hashtable实现的,这个叫做全局Hash表,每个键值对都是一个dictEntry,里面指向了key和value的指针。next指向下一个dictEntry。key是字符串,但是 Redis没有直接使用C的字符数组,而是存储在自定义的SDS
中。value既不是直接作为字符串存储,也不是直接存储在SDS中,而是存储在redisObject中。实际上五种常用的数据类型的任何一种,都是通过redisObject来存储的。
redisObject 源码:

typedef struct redisObject {
unsigned type:4; /* 对象的类型,包括:OBJ_STRING、OBJ_LIST、OBJ_HASH、OBJ_SET、OBJ_ZSET */
unsigned encoding:4; /* 具体的数据结构 */
unsigned lru:LRU_BITS; /* 24 位,对象最后一次被命令程序访问的时间,与内存回收有关 */
int refcount; /* 引用计数。当 refcount 为 0 的时候,表示该对象已经不被任何对象引用,则可以进行垃圾回收了
*/
void *ptr; /* 指向对象实际的数据结构 */
} robj;

1、String字符串

(1)存储类型

String类型可以用来存储字符串、整数、浮点数。

(2)操作命令

##带参数的命令 EX表示秒,PX表示毫秒,都是用来设置过期时间的,
##NX表示只在键不存在时, 才对键进行设置操作,等同于SETNX
##XX只在键已经存在时, 才对键进行设置操作。
set key value [expiration EX seconds|PX milliseconds][NX|XX]
#示例
set test abc EX 10 NX#设置多个值(批量操作,原子性)
mset key1 a key1 b
#取多个值
mget key1 key2
#(整数)值递增
incr testkey
#(整数)值递增100
incrby testkey 100
#(整数)值递减
decr testkey
decrby testkey 100
#浮点数增量
set fkey 2.6
incrbyfloat fkey 7.3
#获取值长度
strlen key1	
#字符串追加内容
append key1 hello
#获取指定范围的字符 0 -1 表示取所有
getrange key1 0 8

(3)底层原理

String类型的内部编码有三种:
1、int,存储 8 个字节的长整型(long,2^63-1)。
2、embstr, 存储小于 44 个字节的字符串。
3、raw,存储大于 44 个字节的字符串(3.2 版本之前是 39 字节)。
注:为啥是39可以查看该文章:https://www.zhihu.com/question/25624589
使用以下命令查看编码

#查看对外的类型,例如Sting、list
type k1
#查看内部的数据结构 例如embstr int
object encoding k1

其中embstr和raw实际上都是使用SDS(Simple Dynamic String 简单动态字符串)来存储的。
在这里插入图片描述
SDS
Redis 中字符串的实现。在3.2以后的版本中,SDS又有多种结构:sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64,用于存储不同的长度的字符串,分别代表 25=32byte、28=256byte、216=65536byte=64KB、232=4GB。
SDS源码:

/* sds.h */
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* 当前字符数组的长度 */
uint8_t alloc; /*当前字符数组总共分配的内存大小 */
unsigned char flags; /* 当前字符数组的属性、用来标识到底是 sdshdr8 还是 sdshdr16 等 */
char buf[]; /* 字符串真正的值 */
};

SDS与C的字符数组对比
C 语言本身没有字符串类型(只能用字符数组 char[]实现)。
字符数组特点:
1、使用字符数组必须先给目标变量分配足够的空间,否则可能会溢出。
2、如果要获取字符长度,必须遍历字符数组,时间复杂度是 O(n)。
3、C 字符串长度的变更会对字符数组做内存重分配。
4、通过从字符串开始到结尾碰到的第一个’\0’来标记字符串的结束,因此不能保存图片、音频、视频、压缩文件等二进制(bytes)保存的内容,二进制不安全。

SDS的特点:
1、不用担心内存溢出问题,如果需要会对SDS进行扩容。
2、获取字符串长度时间复杂度为O(1),因为定义了len属性。
3、通过“空间预分配”( sdsMakeRoomFor)和“惰性空间释放”,防止多次重分配内存。
4、判断是否结束的标志是 len 属性(它同样以’\0’结尾是因为这样就可以使用C语言中函数库操作字符串的函数了),可以包含’\0’。

空间预分配:
空间预分配是用于优化 SDS 字符串增长操作的,简单来说就是当字节数组空间不足触发重分配的时候,总是会预留一部分空闲空间。
惰性空间释放:
惰性空间释放是用于优化 SDS 字符串缩短操作的。简单来说就是当字符串缩短时,并不立即使用内存重分配来回收多出来的字节,而是用 free 属性记录,等待将来使用。

C字符数组SDS
获取字符串长度的复杂度为 O(N)获取字符串长度的复杂度为 O(1)
API 是不安全的,可能会造成缓冲区溢出API 是安全的,不会早晨个缓冲区溢出
修改字符串长度N次必然需要执行N次内存重分配修改字符串长度 N 次最多需要执行 N 次内存重分配
只能保存文本数据可以保存文本或者二进制数据
可以使用所有<string.h>库中的函数可以使用一部分<string.h>库中的函数

embstr和raw
1、embstr的使用只分配一次内存空间(因为RedisObject和SDS是连续的),而raw需要分配两次内存空间(分别为 RedisObject和 SDS分配空间)。因此embstr相比于raw,好处是在创建和删除时都会少操作一次空间分配或释放,而且空间是连续的,寻找方便。embstr的坏处是如果字符串的长度增加需要重新分配内存时,整个RedisObject和SDS都需要重新分配空间,因此Redis中的embstr实现为只读。

编码转换
1、当int数据不再是整数 , 或大小超过了long的范围(2^63-1)时,自动转化为embstr。
2、对于embstr,由于其实现是只读的,因此在对embstr对象进行修改(append)时,都会先转化为raw再进行修改。因此,只要是修改embstr对象,修改后的对象一定是raw的,无论是否达到了44个字节。
3、编码转换在Redis写入数据时完成,且转换过程不可逆,只能从小内存编码向大内存编码转换(但是不包括重新set)。

(4)使用场景

1、缓存。热点数据缓存
2、数据共享分布式。分布式 Session。
3、分布式锁。使用setnx
4、全局ID、计数器。使用INCRBY
5、位计算。使用BitMaps

2、Hash哈希

(1)存储类型

Redis的Hash类型适用于存储对象、字典等有多个子项的场景,类似于java中的map,每一个子项都是一个键值对。如下图所示。
在这里插入图片描述
value 只能是字符串,不能嵌套其他类型。
同样是存储字符串,Hash相比于String的优缺点:
优点:
1、把相关的数据聚集到同一个key中,节省空间,并且减少key冲突。
2、获取批量值时,只需要一个命令,减少I/O操作的事件。
缺点:
1、field不能单独设置过期时间
2、没有bit操作

(2)操作命令

#设置值 hset key field value
hset k1 f1 a
#批量设置
hmset k1 f2 b f3 c f4 d
#获取
hget k1 f1
#批量获取
hmget k1 f1 f2 f3
#拿到k1的所以field
hkeys k1
#拿到k1的所有value
hvals k1
#拿到k1的所以 field-value
hgetall k1
#判断f1是否存在于k1中
hexists k1 f1
#删除field
hdel k1 f1 f2
#计算field的数量
hlen k1

(3)底层原理

Redis本身的K-V的实现使用了hashtable,称为全局哈希或者外层哈希(具体参考上文redis底层原理图)。而Redis的Hash本身也是一个K-V 的结构,类似于Java中的HashMap,称为内层的哈希,内层的哈希底层使用了两种数据结构:
ziplist:OBJ_ENCODING_ZIPLIST(压缩列表)
hashtable:OBJ_ENCODING_HT(哈希表)

ziplist 压缩列表
ziplist 是一个经过特殊编码的双向链表,它不存储指向上一个链表节点和指向下一个链表节点的指针,而是存储上一个节点长度和当前节点长度,通过牺牲部分读写性能,来换取高效的内存空间利用率,是一种时间换空间的思想。只用在字段个数少,字段值小的场景里面。

内部结构如下:
ziplist.c 源码第 16 行的注释:
*<zlbytes> <zltail> <zllen> <entry> <entry> … <entry> <zlend>
在这里插入图片描述
zlentry源码:

typedef struct zlentry {
unsigned int prevrawlensize; /* 上一个链表节点占用的长度 */
unsigned int prevrawlen; /* 存储上一个链表节点的长度数值所需要的字节数 */
unsigned int lensize; /* 存储当前链表节点长度数值所需要的字节数 */
unsigned int len; /* 当前链表节点占用的长度 */
unsigned int headersize; /* 当前链表节点的头部大小(prevrawlensize + lensize),即非数据域的大小 */
unsigned char encoding; /* 编码方式 */
unsigned char *p; /* 压缩链表以字符串的形式保存,该指针指向当前节点起始位置 */
} zlentry;

编码 encoding(ziplist.c 源码第 204 行)
#define ZIP_STR_06B (0 << 6) //长度小于等于 63 字节
#define ZIP_STR_14B (1 << 6) //长度小于等于 16383 字节
#define ZIP_STR_32B (2 << 6) //长度小于等于 4294967295 字节

在这里插入图片描述

ziplist特点
1、ziplist结构本身就是一个连续的内存块,空间连续,避免内存碎片,节省内存。
2、也由于是使用的连续内存,不适合存储存储太大或者元素个数太多,这样会导致申请的内存块太大,使用起来不灵活
3、每次插入或删除一个元素时,都需要进行频繁的进行内存的扩展或减小,然后进行数据”搬移”,甚至可能引发连锁更新,造成严重效率的损失。

使用ziplist
当hash对象同时满足以下两个条件的时候,使用ziplist编码:
1、所有的键值对的健和值的字符串长度都小于等于64byte(一个英文字母
一个字节)
2、哈希对象保存的键值对数量小于512个。

hashtable
在Redis中,hashtable被称为字典(dictionary),它是一个数组+链表的结构。Redis的K-V结构是通过一个dictEntry来实现的,而Redis通过对dictEntry进行多次封装,构成Hash底层的另一种结构hashtable。

dictEntry源码:

typedef struct dictEntry {void *key; /* key 关键字定义 */union {void *val; uint64_t u64; /* value 定义 */int64_t s64; double d;} v;struct dictEntry *next; /* 指向下一个键值对节点 */
} dictEntry;

dictEntry 放到了 dictht:

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

ht 放到了 dict 里面:

typedef struct dict {dictType *type; /* 字典类型 */void *privdata; /* 私有数据 */dictht ht[2]; /* 一个字典有两个哈希表 */long rehashidx; /* rehash 索引 */unsigned long iterators; /* 当前正在使用的迭代器数量 */
} dict;

从最底层到最高层 dictEntry——dictht——dict.
原理如下图所示。
在这里插入图片描述

注:dictht 后面是NULL说明第二个 ht 还没用到。dictEntry*后面是NULL说明没有hash到这个地址。dictEntry 后面是NUL 说明没有发生哈希冲突。

扩容/缩容——rehash
Redis的Hash默认使用的是 ht[0],ht[1]不会初始化和分配空间。哈希表dictht是用链地址法来解决碰撞问题的,所以哈希表的性能取决于它的大小(size 属性)和它所保存的节点的数量(used 属性)之间的比率(也叫负载因子)。

  • 比率在 1:1 时(一个哈希表 ht 只存储一个节点 entry),哈希表的性能最好;
  • 如果节点数量比哈希表的大小要大很多的话(这个比例用 ratio 表示,5 表示平均一个 ht 存储 5 个 entry),那么哈希表就会退化成多个链表,哈希表本身的性能优势就不再存在。

在这种情况下需要扩容。相反的,当元素数量比较少的时候就需要缩容以节约不必要的内存。Redis 里面的这种操作叫做rehash。具体步骤如下:

1、为ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及 ht[0]当前包含的键值对的数量。
扩容,那么ht[1] 的大小为第一个大于等于ht[0] .used*2的2的n次幂
缩容,那么ht[1] 的大小为第一个大于等于ht[0].used 的2的n次幂
2、将所有的 ht[0]上的节点 rehash 到 ht[1]上,重新计算 hash 值和索引,然后放入指定的位置。
3、当 ht[0]的数据全部迁移到了ht[1]之后,释放 ht[0]的空间,将 ht[1]设置为 ht[0]表,然后创建新的 ht[1],为下次 rehash 做准备。

触发rehash的条件:
扩容:(满足任一即可)

  • a)Redis服务器目前没有在执行BGSAVE或BGREWRITEAOF命令,并且哈希表的负载因子大于等于1。
  • b)Redis服务器目前在执行BGSAVE或BGREWRITEAOF命令,并且哈希表的负载因子大于等于5。

缩容:哈希表的负载因子小于0.1

渐进式rehash
在元素数量较少时,rehash会非常快,但是当数据量非常大时,rehash会非常耗时,而且占用资源,可能会导致Redis在一段时间内停止服务。所以rehash这个动作不能一次性、集中式的完成,而是分多次、渐进式地完成。

步骤:
1、为ht[1]分配空间,让dict同时持有ht[0]和ht[1]两个哈希表。
2、在dict中有一个索引计数器变量rehashidx,将它的值设为0,表示rehash工作正式开始。
3、在rehash进行期间,每次对Hash做增删改查操作时,将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],完成后,rehashidx+1。
4、当ht[0]上所有的键值对都被rehash到ht[1]后,将rehashidx属性的值设为-1,表示rehash完成。

(4)使用场景

购物车:
key:用户id
field-value:商品id-商品数量

3、List列表

(1)存储类型

存储有序的字符串,元素可以重复。可以充当队列和栈的角色。数据总容量是有限的,最多 2^32-1个元素 (40 亿左右)。

(2)操作命令

#将一个或多个值value插入到列表的表头(从左插入)
lpush listkey v1 v2
#将一个或多个值value插入到列表的表尾(从右插入)
rpush listkey v1 v2
#从列表的表头移除并返回第一个元素(从左取出)
lpop listkey 
#从列表的表尾移除并返回第一个元素(从右取出)
rpop listkey 
#阻塞式的lpop,当给定列表内没有任何元素可供弹出的时候,连接将被 BLPOP 命令阻塞,直到等待超时或发现可弹出元素为止
blpop listkey 
#阻塞式的rpop,当给定列表内没有任何元素可供弹出的时候,连接将被 BRPOP 命令阻塞,直到等待超时或发现可弹出元素为止
brpop listkey 
#返回列表的长度
llen listkey 
#将listkey下表为0的元素设置为v1
lset listkey 0 v1
#返回列表下标为1的元素(不会移除)
lindex listkey 1
#返回列表指定区间的元素(不会移除)-1表示末尾元素
lrange listkey 0 -1

(3)底层原理

在早期版本中,数据量较小时用ziplist存储,达到临界值时转换为linkedlist进行存储,分别对应 OBJ_ENCODING_ZIPLIST和OBJ_ENCODING_LINKEDLIST。3.2版本之后,统一用quicklist来存储。quicklist存储了一个双向链表,每个节点都是一个ziplist。

quicklist
quicklist(快速列表)是ziplist和linkedlist的结合体。
quicklist源码:

typedef struct quicklist {quicklistNode *head; /* 指向双向列表的表头 */quicklistNode *tail; /* 指向双向列表的表尾 */unsigned long count; /* 所有的 ziplist 中一共存了多少个元素 */unsigned long len; /* 双向链表的长度,node 的数量 */int fill : 16; /* fill factor for individual nodes */unsigned int compress : 16; /* 压缩深度,0:不压缩; */
} quicklist;

quicklist由一个个的quicklistNode双向链表节点构成,head和tail指向双向链表的表头和表尾元素。
quicklistNode源码:

typedef struct quicklistNode {struct quicklistNode *prev; /* 前一个节点 */struct quicklistNode *next; /* 后一个节点 */unsigned char *zl; /* 指向实际的 ziplist */unsigned int sz; /* 当前 ziplist 占用多少字节 */unsigned int count : 16; /* 当前 ziplist 中存储了多少个元素,占 16bit(下同),最大 65536 个 */unsigned int encoding : 2; /* 是否采用了 LZF 压缩算法压缩节点,1:RAW 2:LZF */unsigned int container : 2; /* 2:ziplist,未来可能支持其他结构存储 */unsigned int recompress : 1; /* 当前 ziplist 是不是已经被解压出来作临时使用 */unsigned int attempted_compress : 1; /* 测试用 */unsigned int extra : 10; /* 预留给未来使用 */
} quicklistNode;

原理图如下:
在这里插入图片描述
ziplist原理参考上文Hash的底层原理。

(4)使用场景

1、消息队列。rpush lpop,左进右出。先进先出
2、栈。rpush rpop。坐进左出,先进后出。

4、Set 集合

(1)存储类型

String或者int类型的无序集合,最大存储数量 2^32-1(40 亿左右)。

(2)操作命令

#添加一个或者多个元素
sadd myset a b c d e f g
#获取所有元素
smembers myset
#统计元素个数
scard myset
#随机获取一个元素
srandmember key
#随机移除并返回一个元素
spop myset
#移除一个或者多个元素
srem myset d e f
#查看元素是否存在
sismember myset a

(3)底层原理

Redis用intset或hashtable存储set。如果元素都是整数类型,就用intset存储。如果不是整数类型,就用 hashtable,如果元素个数超过 512 个,也会用 hashtable 存储。

intset
源码:

typedef struct intset {uint32_t encoding; // 编码方式uint32_t length;   // 集合中元素的个数,也就是contents数组的长度int8_t contents[]; // 保存元素的数组
} intset;

contents数组是整数集合的底层实现:整数集合中的每一个元素就是contents数组中的一个元素,每个元素在数组中按照从小到大的顺序排列,并且没有重复元素。

hashtable
hashtable的原理查看上文Hash的底层原理章节。这里不再复述。不同的是set集合里的元素存在key上,value上为null。

(4)使用场景

1、抽奖。spop随机取值
2、点赞、签到、打卡等。用户id集合
3、商品标签。标签集合。
4、商品筛选。不同属性的商品集合取交集、差集、并集。

5、ZSet 有序集合

zset和set一样是不可重复的,区别在于多了score值,用来代表排序的权重,当score相同时,按照 key的 ASCII码排序。

(1)存储类型

同set。

(2)操作命令

#添加元素
zadd testkey 10 a 30 b 40 c 60 d 20 e#获取指定区间内的元素,按score值从小到大
zrange testkey 0 -1 withscores
#获取指定区间内的元素,按score值从大到小
zrevrange testkey 0 -1 withscores
#根据分值区间获取元素 
zrangebyscore testkey 10 40
#移除元素
zrem testkey a b
#统计元素个数
zcard testkey 
#增加元素的sorce值
zincrby testkey 10 d
#根据分值统计个数
zcount testkey 10 60
#获取元素排名
zrank testkey e
#获取元素 score
zsocre testkey d

(3)底层原理

zset的编码有两种,分别是:ziplist、skiplist。当zset的长度小于 128,并且所有元素的长度都小于 64 字节时,使用ziplist存储;否则使用 skiplist 存储。
ziplist
ziplist原理查看上文,这里不再复述。zset使用ziplist时,在ziplist的内部,按照 score 排序递增来存储。插入的时候要移动之后的数据。

skiplist
skiplist也叫跳跃表,了解skiplist前先来看一下有序链表,如下图。
在这里插入图片描述

在有序链表中,如果我们要查找某个数据,那么需要从头开始逐个进行比较,直到找到包含数据的那个节点,或者找到第一个比给定数据大的节点为止(没找到)。也就是说,时间复杂度为 O(n)。同样,当我们要插入新数据的时候,也要经历同样的查找过程,从而确定插入位置。而二分查找法只适用于有序数组,不适用于链表。
这时候假如我们每相邻两个节点增加一个指针,让指针指向下下个节点,这样所有新增加的指针在原来的基础上连成了一个新的链表,但它包含的节点个数只有原来的一半。如果这时候数据量还是很大,再通过这种方式形成一层新的链表,直到最新的链表足够小。
原理如下图所示:
在这里插入图片描述

假如我们要插入70,步骤如下:
1、查询L3层,比较1,45,99,共三次。
2、查询L2层,比较88,共一次
3、查询L1层,比较67,共一次。
4、确定70需要插入在67-88之间。

时间复杂度为O(logN)

(4)应用场景

1.延时队列。score作为时间戳,自动按照时间最近的进行排序,启一个线程持续poll并设置park时间,完成延迟队列的设计。
2.排行榜,score作为浏览次数,自动进行排序,但要注意冷数据。
3.滑动窗口限流,score作为时间戳,可统计最近一段时间内内的成员数量,实现滑动窗口限流。

6、其他数据结构

BitMaps
Bitmaps是在字符串类型上面定义的位操作。一个字节由 8 个二进制位组成。

#a 对应的 ASCII 码是 97,转换为二进制数据是 01100001
set k1 a
#获取k1第0位的bit值
getbit k1 0#修改二进制数据(b 对应的 ASCII 码是 98,转换为二进制数据是 01100010)
setbit k1 6 1
setbit k1 7 0
get k1
#统计二进制位中 1 的个数
bitcount k1#获取第一个 1 或者 0 的位置
bitpos k1 1
bitpos k1 0

Geospatial
地理位置的存储(经纬度)。

#设置地理位置信息
geoadd location 116.39135 39.90737 tiananmen
#获取地理位置信息
geopos location tiananmen

Hyperloglogs
提供了一种不太准确的基数统计方法,可以用来统计app的日活,月活等。HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

Streams
5.0 推出的数据类型。支持多播的可持久化的消息队列,用于实现发布订阅功能,借
鉴了 kafka 的设计。

7、总结

数据结构:

对象对象type属性值type命令输出底层可能的存储结构object encoding
字符串对象OBJ_STRING“string”OBJ_ENCODING_INT、OBJ_ENCODING_EMBSTR、OBJ_ENCODING_RAWint、embstr、raw
列表对象OBJ_LIST“list”OBJ_ENCODING_QUICKLISTquicklist
哈希对象OBJ_HASH“hash”OBJ_ENCODING_ZIPLIST、OBJ_ENCODING_HTziplist、hashtable
集合对象OBJ_SET“set”OBJ_ENCODING_INTSET、OBJ_ENCODING_HTintset、hashtable
有序集合对象OBJ_ZSET“zset”OBJ_ENCODING_ZIPLIST、OBJ_ENCODING_SKIPLISTziplist、skiplist

相关文章:

Redis学习笔记(二)Redis基础(基于5.0.5版本)

一、Redis定位与特性 Redis是一个速度非常快的非关系数据库&#xff08;non-relational database&#xff09;&#xff0c;用 Key-Value 的形式来存储数据。数据主要存储在内存中&#xff0c;所以Redis的速度非常快&#xff0c;另外Redis也可以将内存中的数据持久化到硬盘上。…...

Ancaonda常用cmd命令总结

1) 查看以创建的虚拟环境&#xff1a; conda info --envs / conda env list   2) 激活创建的环境&#xff1a;conda activate xxx(虚拟环境名称)   3) 退出激活的环境&#xff1a;conda deactivate   4) 删除一个已有虚拟环境&#xff1a;conda remove --name(已创建虚拟…...

yolov5_reid【附代码,行人重识别,可做跨视频人员检测】

该项目利用yolov5reid实现的行人重识别功能&#xff0c;可做跨视频人员检测。 应用场景&#xff1a; 可根据行人的穿着、体貌等特征在视频中进行检索&#xff0c;可以把这个人在各个不同摄像头出现时检测出来。可应用于犯罪嫌疑人检索、寻找走失儿童等。 支持功能&#xff1a…...

多模态预训练模型综述

经典预训练模型还未完成后续补上预训练模型在NLP和CV上取得巨大成功&#xff0c;学术届借鉴预训练模型>下游任务finetune>prompt训练>人机指令alignment这套模式&#xff0c;利用多模态数据集训练一个大的多模态预训练模型&#xff08;跨模态信息表示&#xff09;来解…...

华为OD机试题,用 Java 解【玩牌高手】问题

最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…...

数学建模 latex 图片以及表格排版整理(overleaf)

无论是什么比赛&#xff0c;图片和表格的格式都非常重要&#xff0c;这边的重要不只是指规范性&#xff0c;还有抓住评委眼球的能力。 那么怎样抓住评委的眼球&#xff1f; 最重要的一点就是善用图片和表格&#xff08;当然撰写论文最重要的是逻辑&#xff0c;这个是需要长期…...

进程优先级(Linux)

目录 优先级VS权限 基本概念 查看系统进程 几个重要信息 PRI and NI PRI vs NI top命令 上限&#xff1a; 详细步骤 下限&#xff1a; 其他概念 优先级VS权限 权限&#xff1a;能or不能 优先级&#xff1a;已经能&#xff0c;但是谁先谁后的问题&#xff08;CPU资源有…...

[面试直通版]网络协议面试核心之IP,TCP,UDP-TCP与UDP协议的区别

点击->计算机网络复习的文章集<-点击 目录 前言 UDP TCP 区别小总结 前言 TCP和UDP都是在传输层&#xff0c;在程序之间传输数据传输层OSI模型&#xff1a;第四层TCP/IP模型&#xff1a;第三层关键协议&#xff1a;TCP协议、UDP协议传输层属于主机间不同进程的通信传…...

VO,BO,PO,DO,DTO,AO的区别

DTO&#xff08;Data Transfer Object&#xff09;数据传输对象 这个传输通常指的前后端之间的传输 1.在前端的时候&#xff1a; 存在形式通常是js里面的对象&#xff08;也可以简单理解成json&#xff09;&#xff0c;也就是通过ajax请求的那个数据体 2.在后端的时候&…...

JavaSE学习笔记day15

零、 复习昨日 HashSet 不允许重复元素,无序 HashSet去重原理: 先比较hashcode,如果hashcode不一致,直接存储如果hashcode值一样,再比较equals如果equals值为true,则认为完全一样,不存储即去重否则存储 如果使用的是空参构造创建出的TreeSet集合,那么它底层使用的就是自然排序,…...

Spring Security认证研究

1.项目中认证的三种方式&#xff1a; 1.统一认证 认证通过由认证服务向给用户颁发令牌&#xff0c;相当于访问系统的通行证&#xff0c;用户拿着令牌去访问系统的资源。 2.单点登录&#xff0c;对于微服务项目&#xff0c;因为包含多个模块&#xff0c;所以单点登录就是使得用户…...

BigKey、布隆过滤器、分布式锁、红锁

文章目录 BigKey发现 BigKey如何删除BigKeyunlinkdelBigKey配置优化布隆过滤器布隆过滤器构建、使用、减少误判布隆过滤器二进制数组,如何处理删除?实现白名单 whitelistCustomer解决缓存穿透分布式锁依赖Redis 分布式锁代码使用红锁POM依赖yaml使用其他redis分布式锁容错率公…...

一文让你彻底理解Linux内核调度器进程优先级

一、前言 本文主要描述的是进程优先级这个概念。从用户空间来看&#xff0c;进程优先级就是nice value和scheduling priority&#xff0c;对应到内核&#xff0c;有静态优先级、realtime优先级、归一化优先级和动态优先级等概念。我们希望能在第二章将这些相关的概念描述清楚。…...

Java 抽象类和接口

文章目录一、抽象类1. 抽象类定义2. 抽象类成员特点二、接口1. 接口概述2. 接口成员特点3. 类和接口的关系4. 抽象类和接口的区别5. 接口案例三、形参和返回值一、抽象类 1. 抽象类定义 在 Java 中&#xff0c;一个没有方法体的方法应该定义为抽象方法&#xff0c;而类中如果…...

三行代码让你的git记录保持整洁

前言笔者最近在主导一个项目的架构迁移工作&#xff0c;由于迁移项目的历史包袱较重&#xff0c;人员合作较多&#xff0c;在迁移过程中免不了进行多分支、多次commit的情况&#xff0c;时间一长&#xff0c;git的提交记录便混乱不堪&#xff0c;随便截一个图形化的git提交历史…...

阿里巴巴内网 Java 面试 2000 题解析(2023 最新版)

前言 这份面试清单是今年 1 月份之后开始收集的&#xff0c;一方面是给公司招聘用&#xff0c;另一方面是想用它来挖掘在 Java 技术栈中&#xff0c;还有一些知识点是我还在探索的&#xff0c;我想找到这些技术盲点&#xff0c;然后修复它&#xff0c;以此来提高自己的技术水平…...

网络应用之静态Web服务器

静态Web服务器-返回固定页面数据学习目标能够写出组装固定页面数据的响应报文1. 开发自己的静态Web服务器实现步骤:编写一个TCP服务端程序获取浏览器发送的http请求报文数据读取固定页面数据&#xff0c;把页面数据组装成HTTP响应报文数据发送给浏览器。HTTP响应报文数据发送完…...

IndexDB 浏览器服务器

IndexDB 浏览器服务器 文章部分内容引用&#xff1a; https://www.ruanyifeng.com/blog/2018/07/indexeddb.html https://juejin.cn/post/7026900352968425486#heading-15 基本概念 数据库&#xff1a;IDBDatabase 对象对象仓库&#xff1a;IDBObjectStore 对象索引&#xff1…...

追梦之旅【数据结构篇】——详解C语言实现链队列

详解C语言实现链队列~&#x1f60e;前言&#x1f64c;整体实现内容分析&#x1f49e;预备小知识&#x1f64c;1.链队列头文件编写&#x1f64c;2.链队列功能文件&#xff08;Queue.c &#xff09;编写&#xff1a;&#x1f64c;1&#xff09;初始化函数实现2&#xff09;销毁函…...

SpringMVC - 13 - SpringMVC执行流程

文章目录1、SpringMVC常用组件2、DispatcherServlet初始化过程a>初始化WebApplicationContextb>创建WebApplicationContextc>DispatcherServlet初始化策略3、DispatcherServlet调用组件处理请求a>processRequest()b>doService()c>doDispatch()d>processDi…...

6091: 斐波那契数列

描述一个斐波那契序列&#xff0c;F(0) 0, F(1) 1, F(n) F(n-1) F(n-2) (n>2)&#xff0c;根据n的值&#xff0c;计算斐波那契数F(n)。输入输入数据的第一行为测试用例的个数t&#xff0c;接下来为t行&#xff0c;每行为一个整数n&#xff08;2≤n≤40&#xff09;。输出…...

任何人均可上手的数据库与API搭建平台

编写API可能对于很多后端开发人员来说&#xff0c;并不是什么难事儿&#xff0c;但如果您主要从事前端功能&#xff0c;那么可能还是有一些门槛。 那么有没有工具可以帮助我们降低编写API的学习门槛和复杂度呢&#xff1f; 今天就来给大家推荐一个不错的开源工具&#xff1a;…...

Ubuntu(虚拟机)的Anaconda 及使用

安装Anaconda 使用firefox打开Ananconda网址Anaconda | The Worlds Most Popular Data Science Platform 下载后有.sh文件&#xff1a; Anaconda3-2022.10-Linux-x86_64.sh 进入所在目录打开终端并输入 $ bash Anaconda3-2022.10-Linux-x86_64.sh 然后开始安装。 对于给…...

Git ---- IDEA集成 GitHub

Git ---- IDEA集成 GitHub1. 设置 GitHub 账号2. 分享工程到 GitHub3. push 推送本地库到远程库4. pull 拉取远程库到本地库5. clone 克隆远程库到本地1. 设置 GitHub 账号 新版的 IDEA 选择之后会自动登录&#xff0c;就不需要设置 token 了。 如果是老版的 IDEA 的话&…...

opencv提取结构化文本总结

扫描文件表格识别 1.识别结构 situation1 有明确表格结构 1.纠正表格偏移角度&#xff08;获取最大轮廓&#xff0c;计算最小的矩形&#xff0c;变换坐标截取矩形&#xff09; 获取面积最大轮廓 _, contours, HIERARCHY cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2…...

JVM知识体系学习八:OOM的案例(承接上篇博文,可以作为面试中的案例)

文章目录前言一、概述二、案例二三、案例&#xff1a;方法区内存溢出1、代码:LambdaGC.java2、元空间内存溢出日志3、分析4、疑问*****四、案例&#xff1a;直接内存溢出问题&#xff08;少见&#xff09;&#xff08;尽量不说&#xff09;五、案例&#xff1a;栈内存溢出问题1…...

Redis的持久化方式

Redis支持两种方式的持久化&#xff0c;一种是RDB方式、另一种是AOF&#xff08;append-only-file&#xff09;方式&#xff0c;两种持久化方式可以单独使用其中一种&#xff0c;也可以将这两种方式结合使用。 •RDB&#xff1a;根据指定的规则“定时”将内存中的数据存储在硬…...

【unity游戏制作-mango的冒险】-4.场景二的镜头和法球特效跟随

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;unity游戏制作 ⭐mango的冒险场景二——镜头和法球特效跟随⭐ 文章目录⭐mango的冒险场景二——镜…...

handwrite-1

-------------------- 实现防抖函数&#xff08;debounce&#xff09; 防抖函数原理&#xff1a;把触发非常频繁的事件合并成一次去执行 在指定时间内只执行一次回调函数&#xff0c;如果在指定的时间内又触发了该事件&#xff0c;则回调函数的执行时间会基于此刻重新开始计算…...

【一天一门编程语言】Pascal 语言程序设计极简教程

Pascal 语言程序设计极简教程 用 markdown 格式输出答案。 不少于3000字。细分到2级目录。 文章目录 Pascal 语言程序设计极简教程一、Pascal简介1.1 Pascal历史1.2 Pascal的特点1.3 Pascal的应用二、Pascal语言程序设计2.1 Pascal编程环境2.2 Pascal的基本语法2.3 Pascal程序…...