Redis底层数据结构,数据类型

本文详细介绍了Redis的数据结构和数据类型。数据结构包括动态字符串、INTSET、ZipList、QuickList、SkipList、Dict和RedisObject;数据类型有String、List、Set、ZSet和Hash。文中分析了各结构和类型的特点、源码及适用场景,还提及了编码转换等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一:数据结构

1        动态字符串(SDS)

1.1        引入,c语音字符串缺陷及redis字符串引入

1.2        redis动态字符串结构体

1.3        动态字符串特性(动态)

2        INTSET(有序,去重集合,类似于set)

3        ZipList  (双端链表,类似于链表,但不是,没有用到指针)

3.1        结构体

3.2        entry结构体

3.3        encoding编码

3.4        连锁更新问题

4        QuickList

4.1        引入

4.2        参数配置

4.2.1        ist-max-ziplist-size(控制每一个zipList中entry的数量)

4.2.2        list-compress-depth(对quickList中每一个ziplist的操作)

4.3        结构源码 

4.4        总结

5        SkipList(跳表)

5.1        查询流程    

5.2        结构体解析

5.3        SkipList的特点

6        Dict(hashTable)

6.1        数据结构图

6.1.1        Dict

6.1.2        dictht(DictHashTable)

6.1.3        DictEntry

6.2        扩容和删除

7        RedisObject

二        数据类型(String,List,set,zset,Hash)

2.1        String

2.2        List

2.3        Set

2.3.1        set概念

2.3.2        源码解析

2.3.3        数据结构图

2.4        ZSet

2.4.1        ZSet概念,引出编码格式

2.4.2        ZSet类型结构

2.4.2.1        dict和skiplist组成的zset

2.4.2.2        zipList实现

2.4.3        源码解析

2.5        Hash

2.5.1        hash与zset的相同与区别

2.5.2        hash结构图及编码解析

2.5.3        源码解析


一:数据结构

1        动态字符串(SDS)

1.1        引入,c语音字符串缺陷及redis字符串引入

        引入:redis是由c语音编写的,但是Redis没有直接使用C语言中的字符串,尾部会拼接一个(/0)来当结束标记,因为C语言字符串存在很多问题:

1.获取字符串长度的需要通过运算(没有地方存储长度,所以每次都是计算出来的)

2.非二进制安全(字符串中间出现一个/0时,会导致数据不全)

3.不可修改(拼接或截取后的会新创建);

        所以redis构建了一种新的字符串结构,针对上面c的缺点进行优化,动态字符串(sds);set name 'haha',会生成两个sds字符串;

1.2        redis动态字符串结构体

        像java中的一个对象,有四个属性(成员变量),

len : 表示当前字符串的实际长度。

alloc : 字符串申请的长度,不包含结束标记(/0);分两种情况;

        1:第一次申请,就是字符串的实际长度

        2:  非第一次申请,根据变更后的长度进行设置。(这个还要细分,下面解释)

flags : 使用的类不同,0现在被舍弃了,1代表使用的长度是2的8次方,2代表的是2的16次方。

buf[] : 实际存储的数据。

1.3        动态字符串特性(动态)

        字符串的每次扩容,都会涉及到内核和用户核的切换,浪费性能,所以每次扩容都会进行内存预分配,动态字符串的优点正好解决了c语音的问题。每次扩容计算出的长度+1,是为了保存(/0)结束标记,但是实际保存alloc(申请长度)时是不会加这个1的。

        追加完,Amy后,下次再次追加,首先判断新的字符串长度如果还是小于1M,并且追加后的长度小于12,就不用再次进行扩容了;

2        INTSET(有序,去重集合,类似于set)

IntSet是Redis中set集合的一种实现方式,基于整数数组来实现,并且具备长度可变有序等特征。结构如下:也是类似于java的对象

encoding :编码格式

length :记录存储的元素个数

contents :数据存储

数据都是有序存储的,占据的长度也都是一样的(根据编码格式确定) ,所以方便查找;

        增加一个50000的数,这个不在int16的范围了,需要升级编码格式;首先判断出50000属于哪一个编码格式范围,然后开始按照新的编码格式进行升级扩容;其次,将数组中原先的数据进行倒序处理,注意,要预留50000这个数的位置;等待原数组中的数据迁移完毕后,再将50000进行处理(-50000就会放在头部);最后,去改动编码及length数据。

3        ZipList  (双端链表,类似于链表,但不是,没有用到指针)

        由一系列特殊编码的连续内存块组成。可以在任意一端进行压入/弹出操作, 并且该操作的时间复杂度为 O(1)。查询慢,增删快,链表特征

3.1        结构体

zlpBytes:整体长度

zlTail :起始位置到最后一个entry节点位置的长度

zllen:entry节点个数

zlend:结束标识符

3.2        entry结构体

        entry结构体中保存的节点长度,方便我们快速定位到节点最后或者最前的节点位置,进行增删操作;

3.3        encoding编码

 ZipListEntry中的encoding编码分为字符串和整数两种:

字符串:如果encoding是以“00”、“01”或者“10”开头,则证明content是字符串

整数:如果encoding是以“11”开始,则证明content是整数,且encoding固定只占用1个字节,其中,0到12的数字可以直接在位置上存储;       

3.4        连锁更新问题

        ZipList的每个Entry都包含previous_entry_length来记录上一个节点的大小,长度是1个或5个字节:

        如果前一节点的长度小于254字节,则采用1个字节来保存这个长度值

        如果前一节点的长度大于等于254字节,则采用5个字节来保存这个长度值,第一个字节为0xfe,后四个字节才是真实长度数据

        现在,假设我们有N个连续(这个是重点,很少)的、长度为250~253字节之间的entry,因此entry的previous_entry_length属性用1个字节即可表示;但是现在要新增或者删除(被删除节点的前一个节点的长度是255,被删除节点及其后的都是250)一个,新增一个长度大于254的,就会导致后面的所有entry对象中的previous_entry_length(前一节点长度)的字节数变成5个,连锁影响后面的所有entry对象,就是连锁更新问题;会导致操作系统的性能浪费和内存扩张的频繁请求;

4        QuickList

4.1        引入

问题1:ZipList虽然节省内存,但申请内存必须是连续空间,如果内存占用较多,申请内存效率很低。怎么办?

为了缓解这个问题,我们必须限制ZipList的长度和entry大小。

问题2:但是我们要存储大量数据,超出了ZipList最佳的上限该怎么办?

我们可以创建多个ZipList来分片存储数据。

问题3:数据拆分后比较分散,不方便管理和查找,这多个ZipList如何建立联系?

Redis在3.2版本引入了新的数据结构QuickList,它是一个双端链表,只不过链表中的每个节点都是一个ZipList

4.2        参数配置
4.2.1        ist-max-ziplist-size(控制每一个zipList中entry的数量)

        为了避免QuickList中的每个ZipList中entry过多,Redis提供了一个配置项:list-max-ziplist-size来限制。

(一)如果值为正,则代表ZipList的允许的entry个数的最大值

(二)如果值为负,则代表ZipList的最大内存大小,

分5种情况:

-1:每个ZipList的内存占用不能超过4kb

-2:每个ZipList的内存占用不能超过8kb

-3:每个ZipList的内存占用不能超过16kb

-4:每个ZipList的内存占用不能超过32kb

-5:每个ZipList的内存占用不能超过64kb  其默认值为 -2: 可通过config get list-max-ziplist-size获取查看。

4.2.2        list-compress-depth(对quickList中每一个ziplist的操作)

除了控制ZipList的大小,QuickList还可以对节点的ZipList做压缩。通过配置项list-compress-depth来控制。因为链表一般都是从首尾访问较多,所以首尾是不压缩的。

这个参数是控制首尾不压缩的节点个数:

0:特殊值,代表不压缩

1:标示QuickList的首尾各有1个节点不压缩,中间节点压缩

2:标示QuickList的首尾各有2个节点不压缩,中间节点压缩; 以此类推 

4.3        结构源码 

*head和*tail分别指向quickList中第一个和最后一个zipList的位置,count是指此quickList中所有zipList中entry对象和数量;len是指此quickList中zipList的数量;fill就是2.1中的数量内存控制,默认是-2;cpmpress是压缩,详见2.2中的命令

*prev和*next是指quickList中,zipList是一个个的节点,用来连接zipList;每一个ziplist中sz加起来的合计就是左图中的count;encoding是左图中的fill控制的; 

4.4        总结

QuickList的特点:

是一个节点为ZipList的双端链表

节点采用ZipList,解决了传统链表的内存占用问题(传统链表只采用了指针)

控制了ZipList大小,解决连续内存空间申请效率问题(命令1)

中间节点可以压缩,进一步节省了内存(命令2)。

5        SkipList(跳表)

SkipList(跳表)首先是链表,但与传统链表相比有几点差异:

元素按照升序排列存储(顺序存储,从小到大,才能支持这种跳表)

节点可能包含多个指针,指针跨度不同。(同一个节点可以有多个层级指针)

首先数据在zipList中是按从小到大的顺序排列的;

每一个zipList必然有一级指针,这是最基本的,这种查询慢,需要一个个去便利查询;

每一个节点又可以有多极指针,但是,在查询的时候,一定是从最大的层级开始比对;

5.1        查询流程    

        假如现在要查询12,首先查看zipList中的level最大的层级是多少,现在是4,所以就先从4级指针开始比对,在一个节点是10,比较后,比10大;10节点这里扩展下,还有个到20的zskpiList节点,在此比较后小于20,就进入三级指针,发现是到节点15的,比这个小,就进入二级指针,发现是13,比较后,小,就进入一级指针,发现后一个节点是11,12比11大,就往后再查询一个,到12;

5.2        结构体解析

        zipList还是我们最原本的数据结构,*header,*tail就是前一个和后一个节点的指针信息,length是zipList中节点的长度,也就是一共有几个节点,level是指针层级,默认是1,基础层级;

zskipList,ele节点中存储的数据,是一个动态字符串;*backward是前一个节点指针的位置信息,方便进行倒叙查询;zskipListLevel是一个数组,比如1节点,他一共有四个层级信息,span是跨度,比如三级层级中的5节点,他的下一个节点是10,跨度就是5;*forward,是下一个指针的信息,就是10,方便进行查询,增加查询效率,方便跳表查询;

5.3        SkipList的特点

跳跃表是一个双向链表,每个节点都包含score和ele值

节点按照score值排序,score值一样则按照ele字典排序

每个节点都可以包含多层指针,层数是1到32之间的随机数(包含最底下一层的话就是32层)

不同层指针到下一个节点的跨度不同,层级越高,跨度越大(越上层的跨度越大)

增删改查效率与红黑树基本一致(强啊),实现却更简单。

6        Dict(hashTable)

        我们知道Redis是一个键值型(Key-Value Pair)的数据库,我们可以根据键实现快速的增删改查。而键与值的映射关系正是通过Dict来实现的。 Dict由三部分组成,分别是:

哈希表(DictHashTable)、哈希节点(DictEntry)、字典(Dict)。

6.1        数据结构图

6.1.1        Dict

dict中的:*type,*privdata,是进行hash运算时的一些固定配置;

ht[],存储数据用,数据一般默认存储在ht[0]中,只有在需要进行伸缩(扩容和删除)时,才会用到ht[1];

rehashidx,默认-1,表示没有在进行伸缩操作;

pauuserehash:也是用于rehash的,表示rehash操作的;

6.1.2        dictht(DictHashTable)

**table:两个*,代表指向的是一个数组对象;

size:hash表的大小,数组的个数,不包含数组下包含的链表数据;大小是2的n次方--2,4,8....;

sizemask:总等于size-1;

used:实际使用的entry个数;

6.1.3        DictEntry

*key:键值信息;

*next:指向下一个entry对象的位置;

v:这个值信息可以是任何数据类型的数据;

6.2        扩容和删除

        Dict中的HashTable就是数组结合单向链表的实现,当集合中元素较多时,必然导致哈希冲突增多,链表过长,则查询效率会大大降低。 Dict在每次新增键值对时都会检查负载因子(LoadFactor = used/size) ,满足以下两种情况时会触发哈希表扩容:

哈希表的 LoadFactor >= 1,并且服务器没有执行 BGSAVE 或者 BGREWRITEAOF 等后台进程; 哈希表的 LoadFactor > 5 (这时候会强制扩容);

过程:(只有在新增或者删除的才会进行伸缩操作)

        首先判断是否正在进行rehash,进行中,就取消本次rehash,反之,判断ht[0]中的元素个数是不是0,是0,还没有新增过,初始化哈希表(默认是4),然后,用ht[0]的size个数和实际使用数(used)进行除法运算,判断扩容因子的大小,满足上面的扩容因子条件后,进行扩容;假如是新增操作,现在的数据长度是8(size),实际使用数(used)是7(通过链表的形式存储在了结构体上,java中的hashmap),现在又加了一个,used变成8,负载因子就等于一了,可以扩容了,排除执行bgsave等命令的情况;这时size+1,变成9,然后找出大于等于9的最接近的一个数(2的n次方),是16,所以size就扩容成16;

        删除的时候同样也会,判断扩容因子是不是<0.1,小于,会做哈希表收缩;

判断完需要进行扩容删减后,就可以执行下面的操作了;

        不管是扩容还是收缩,必定会创建新的哈希表,导致哈希表的size和sizemask变化,而key的查询与sizemask有关。因此必须对哈希表中的每一个key重新计算索引,插入新的哈希表,这个过程称为rehash。

        过程是这样的: 计算新hash表的realeSize,值取决于当前要做的是扩容还是收缩: 如果是扩容,则新size为第一个大于等于dict.ht[0].used + 1的2^n 如果是收缩,则新size为第一个大于等于dict.ht[0].used的2^n (不得小于4)

        按照新的realeSize申请内存空间,创建dictht,并赋值给dict.ht[1]

        设置dict.rehashidx = 0,标示开始rehash

        将dict.ht[0]中的每一个dictEntry都rehash到dict.ht[1](每次新增删除的时候自操只一个元素位置上的数据:索引位置及其下属的链表数据);每次执行新增、查询、修改、删除操作时,都检查一下dict.rehashidx(是否在进行rehash操作)是否大于-1,如果是,则将dict.ht[0].table[rehashidx]的entry链表rehash到dict.ht[1],并且将rehashidx++。直至dict.ht[0]的所有数据都rehash到dict.ht[1](这就是渐进式rehash)

        将dict.ht[1]赋值给dict.ht[0],给dict.ht[1]初始化为空哈希表,释放原来的dict.ht[0]的内存(交换了位置)

        将rehashidx赋值为-1,代表rehash结束。

        在rehash过程中,新增操作,则直接写入ht[1],查询、修改和删除则会在dict.ht[0]和dict.ht[1]依次查找并执行。这样可以确保ht[0]的数据只减不增,随着rehash最终为空

7        RedisObject

可以说这个机构体是将之前的几种数据结构进行的封装;

type:数据类型(string,list,set,zset,hash)

encoding:编码格式

lru:最后一次访问时间,方便进行内存回收

refcount:引用数量,引用一次就自增一;等于0时,可以被回收

*pre:指针,指向我们存储数据的空间位置,实际存数据的地方

        可以看出,除了*pre之外,这个对象就已经占用了一二十个字节了,所以建议我们在使用的时候尽量少使用string,尽量多使用其他的数据类型,可以多存储一些数据,但是只用了一个指针,只用了这一二十个字节,节省空间;

编码格式:

数据类型对应的数据结构:

二        数据类型(String,List,set,zset,Hash)

2.1        String

        有三种不同的编码格式:

        1:其基本编码方式是RAW,基于简单动态字符串(SDS)实现,存储上限为512mb。字符串的长度大于44了;这种是需要申请两次内存地址的,一次是redisObject,一次是ptr所指向的SDS动态字符串,这两个对象是独立的内存空间地址;这种寻值时是需要浪费性能的;需要通过ptr指针才能找到真实的数据。

        2:如果存储的SDS长度小于44字节,则会采用EMBSTR编码,此时object head与SDS是一段连续空间。申请内存时只需要调用一次内存分配函数,效率更高。这种格式与redis底层的内存非配有关系,都是以2的N次方去获取内存存储空间的,redisObject对象加上后面的sds字符串的长度正好控制在了64之内,也就是2的6次方以内;所以这种结构就不需要ptr再去指向sds的位置空间了,直接连接在redisObject对象后面,增加使用效率。

        3:如果存储的字符串是整数值,并且大小在LONG_MAX范围内,则会采用INT编码:直接将数据保存在RedisObject的ptr指针位置(刚好8字节),不再需要SDS了。

测试:

分别set name 为字符串java;  Java。。。大于44为止;  123;通过下面的命令查询name的编码格式;OBJECT ENCODING name,分别得到,        embstr,raw,int。

2.2        List

LinkedList :普通链表,可以从双端访问,内存占用较高,内存碎片较多

ZipList :压缩列表,可以从双端访问,内存占用低,存储上限低

QuickList:LinkedList + ZipList,可以从双端访问,内存占用较低,包含多个ZipList,存储上限高所以,List底层用的是QuickList数据结构;

在3.2版本之前,Redis采用ZipList和LinkedList来实现List,当元素数量小于512并且元素大小小于64字节时采用ZipList编码,超过则采用LinkedList编码。

在3.2版本之后,Redis统一采用QuickList来实现List。

2.3        Set

Set是Redis中的单列集合,满足下列特点:

不保证有序性         保证元素唯一         求交集、并集、差集。

2.3.1        set概念

        HashTable,也就是Redis中的Dict,不过Dict是双列集合(可以存键、值对),我们只在key上存储数据就行,值的位置上全部设置为null;类似于java中的hashSet,就是只用了hashMap的key;

        1:为了查询效率和唯一性,set采用HT编码(Dict)。Dict中的key用来存储元素,value统一为null。

        2:当存储的所有数据都是整数,并且元素数量不超过set-max-intset-entries时,默认是512,Set会采用IntSet(连续的内存空间,就要求数据量小了)编码,以节省内存

2.3.2        源码解析

        传递一个动态字符串参数进来,如果是long类型的,就创建一个intSet编码的redisObject对象,分配内存空间,并且制定类型为set,然后指定编码格式为intset。

        如果value不是long类型的,就直接创建Dict(字典)类型的redisObject,获取内存空间。然后设置编码格式为set。

        我们将编码格式设置为intset,后面存储的数据不可能一致都是long类型的整数,且长度有可能超过512,这时候,就涉及到intset向Dict转换了,这个操作是在添加元素的时候进行的;

        对象创建完成后,就可以进行添加操作,添加方法进去后,首先判断编码格式是不是Dict,是的话,直接执行添加操作,反之,编码是intset的,但是传入的value不是long类型的整数,这时候就需要转化成Dict了,如果传入的value是long类型的整数,就进行添加操作,添加成功后,判断intset的长度是不是超过最大限制(512)了,或者我们调整过这个最大限制,导致过了512也没有进行转化,但是,源码有一个兜底操作,最大不能大于2的30次方,超过也会进行编码转化。

2.3.3        数据结构图

        1:当存储的所有数据都是整数,并且元素数量不超过set-max-intset-entries时,Set会采用IntSet编码,以节省内存。

        这种结构中,redisObject对象的类型是set,但是编码是intset,ptr指向的是一个intset类型的数据。

        2:为了查询效率和唯一性,set采用HT编码(Dict)。Dict中的key用来存储元素,value统一为null。长度超过自己的设置数,或者超过源码设置的最大数(2的30次方),会采用Dict编码。

        这种结构中,redisObject对象的类型是set,但是编码是Ht,也就是,ptr指向的是一个Dict类型的数据。dict中分为两个ht,一个是存储数据的,另一个是进行rehash用的。

2.4        ZSet

        ZSet也就是SortedSet,其中每一个元素都需要指定一个score值和member值:

        可以根据score值排序后 ,member必须唯一 ,可以根据member查询分数

2.4.1        ZSet概念,引出编码格式

        因此,zset底层数据结构必须满足键值存储、键必须唯一、可排序这几个需求。之前学习的哪种编码结构可以满足?

SkipList:可以排序,并且可以同时存储score和ele值(member),但是无法进行键值查询

HT(Dict):可以键值存储,并且可以根据key找value,但是无法进行排序操作,底层是hash;

        两种结构都无法全部满足ZSet的需求,所以就需要联合两个结构来实现,所以,ZSet的内存占用非常庞大,需要将数据存储在两种结构中,以空间换时间;

2.4.2        ZSet类型结构

        有两种使用方式,一种是由dict和skpiList组成的,这种比较浪费内存,因为使用了两种结构;两外一种是zipList,使用连续的内存空间来实现。

2.4.2.1        dict和skiplist组成的zset

        zset中,包含了两部分,dict(字典)和zskipList(跳表),在整个redisObject对象中,类型为zset类型,编码为skipList,ptr分别指向了两个地址,dict和skipList两个位置,这两个位置上都存储了数据,内存的消耗是比较大的,因为别的都是用一种编码格式就解决了;所以,为了节省内存空间,还有另一种方式。

2.4.2.2        zipList实现

        需要连续的内存空间,这种,排序后,再将挨着的两个entry对象,一个作为key(在前),一个作为score(在后),遍历放进对象中,浪费时间,牺牲时间保存空间。不过需要同时满足两个条件:这种会涉及到编码转化,详见源码解析

         元素数量小于zset_max_ziplist_entries,默认值128

        每个元素都小于zset_max_ziplist_value字节,默认值64

2.4.3        源码解析

        首先,根据key判断zset是否存在,存在,就进行添加;不存在,然后在判断元素数量是不是0(禁用zipList),或者value的大小超过了元素字节限制,这种就用HT+SkipList的组合形式;反之就采用zipList这种节省内存空间的编码格式。

        根据上面的判断,就知道我们需要创建何种类型的编码格式了,开始创建对应的redisObject对象,zipList的先分配一个zipList的内存空间,创建类型为zset的redisObject对象,然后指定编码格式为zipList结束;对于组合形式的,先获取内存空间,然后分别创建Dict和ZskipList的空间指针,然后指定类型为ZSet,编码格式为skipList;

        添加操作在执行的时候,判断编码格式,如果是skipList的,直接执行添加操作;如果是zipList的,如果当前元素已存在,只需要更新score就行了,反之这个元素不存在,执行添加操作,判断是否元素个数是否超限制(默认会成功,所以直接是原来的长度+1去进行比对),元素的字节个数是否超限制,任何一个超了,都会执行转化操作,反之,进行添加操作。

2.5        Hash

2.5.1        hash与zset的相同与区别

        Hash结构与Redis中的Zset非常类似:         

        都是键值存储, 都需求根据键获取值 ,键必须唯一

        区别如下: zset的键是member,值是score;hash的键和值都是任意值 zset要根据score排序;hash则无需排序。

        命令:hset h1 name java age 99        zadd z1 10 a 20 b

2.5.2        hash结构图及编码解析

        Hash底层采用的编码与Zset也基本一致,只需要把排序有关的SkipList去掉即可:        

        1:Hash结构默认采用ZipList编码,用以节省内存。 ZipList中相邻的两个entry 分别保存field和value;

        2:当数据量较大时,Hash结构会转为HT编码,也就是Dict,触发条件有两个:

ZipList中的元素数量超过了hash-max-ziplist-entries(默认512)

ZipList中的任意entry大小超过了hash-max-ziplist-value(默认64字节)。

2.5.3        源码解析

        方法名中**Command,这种带command的,传递的参数client,是一个数组类型的参数,里面封装了我们所执行的命令;

        首先判断key对应的hash是否存在,不存在就创建一个redisObject对象返回,只有hash的这种比较特殊,它是一个方法就将判断和创建整合了,但是这个整合的方法中,还是两个主要的方法,一个lookup判断方法,和其他类型一样,另一个就是创建redisObject的方法;类型为hash,编码格式默认为ziplist。

        redisObject对象创建完成后,默认使用的是zipList的编码格式,但是,不一定符合实际情况,需要根据传入的value进行解析判断;如果redisObject的编码格式不是zipList,那就不用进行编码转换(一定是之前就转化过了,变成了dict);然后根据传入的start(第一个field,也就是name),end(操作命令的长度-1)进行for循环,里面的end有等于,依次判断操作命令中的field,value的元素字节长度是否超限制,是否是sds字符串(字符串或整数),超了就转换成dict类型,这里不判断元素的个数,是因为这个方法只是预添加,不是真正的添加操作;如果元素长度和元素类型都校验后,再最后兜底判断,field和value的合计是否超1G,超了也需要转换成HT。

        预添加判断完成后,执行添加操作,方法中也需要有编码格式转换的判断;通过循环传入field和value;首先,编码如果是Ht的,直接执行添加操作;反之是zipList的,先查询zipList中的第一个元素,为空,说明zipList中没有数据,是一个空的,直接执行zipList的添加操作(元素长度在预添加方法中已完成判断),然后判断zipList长度是否超限制,超了就进行编码格式转换;如果zipList的第一个元素不是空的,就判断下这个要添加的数据(field)是不是在zipList已存在,存在就执行修改操作,反之就和ziplist第一个元素是空的逻辑差不多相同,就开始执行zipList长度判断操作,添加不需要执行,因为这个field是修改操作。

Redis是一个开源的内存数据库,它使用了多种数据结构来存储不同类型的数据。下面是几种常见的Redis底层数据结构的详解: 1. 字符串(String):字符串是Redis中最基本的数据结构。它可以存储任意类型的数据,包括数字、文本等。字符串在Redis中以字节数组的形式存储,可以通过键访问和修改。 2. 列表(List):列表是一个有序的字符串集合,可以在列表的两端进行插入、删除和获取操作。Redis使用双向链表来实现列表数据结构,它支持快速插入和删除操作。 3. 哈希(Hash):哈希是一种键值对的集合。在Redis中,哈希可以存储多个字段和对应的值,类似于关联数组或者字典。哈希在内部使用哈希表来实现,可以快速查找和修改字段值。 4. 集合(Set):集合是一组唯一且无序的字符串集合。Redis使用哈希表来实现集合数据结构,它支持添加、删除和判断元素是否存在等操作。 5. 有序集合(Sorted Set):有序集合是一组唯一且有序的字符串集合。在Redis中,每个元素都会关联一个分数,通过分数可以对元素进行排序。有序集合的实现使用了跳跃表和哈希表两种数据结构,它支持添加、删除、修改和范围查询等操作。 这些数据结构底层实现都是高效的,并且支持丰富的操作。Redis数据结构灵活性较高,能够满足不同类型的数据存储需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值