学习了Redis的五大数据类型,其底层实现你了解吗?
文章目录
- String底层
- List底层
- Hash底层
- Set底层
- Zset底层
String底层
String的底层并不是简单的C字符串简单动态字符串(Simple Dynamic String,SDS)。
SDS简介
Redis是用C语言写的,但是他的字符串竟然不是C字符串,这让我很惊讶。既然不是C字符串,那一定有一个数据类型吧,这个数据类型是简单动态字符串(Simple Dynamic String,SDS),这是什么鬼,第一次听说。既然用他,他肯定有厉害的地方,接下来就研究研究他厉害到哪!
先举个例子说说字符串。
127.0.0.1:6379> set msg "hello"
OK
127.0.0.1:6379> keys *
1) "msg"
上面的"msg"和"hello"就是字符串,而且是一个SDS字符串,可不是一个C字符串。
SDS的内部存储结构
好了,说了这么多,还没介绍啥是SDS呢,先说一下SDS的内部存储结构,其中一个SDS,内部包含三部分
char buf[];// 字节数组,用于保存字符串
int len; // buf数组中已使用的字节数量,即是SDS字符串的长度
int free;// buf数组中未使用的字节数量
C字符串的内部存储结构
char str[];// 其内部就是一个简单的字节数组
SDS和C字符串的区别
获取字符串长度
由于C字符串没有哦记录自身的长度信息,所以获取C字符串长度的时候,必须遍历整个字符串,其时间复杂度是O(n),而SDS中有len属性,所以在获取其长度时,时间复杂度为O(1)。
缓冲区溢出
由于C字符串不记录字符串的长度,当我们进行字符串拼接的时候,可能会出现缓冲区溢出问题,举个例子,我们有s1和s2两个字符串,当我们想要把s2字符串拼接到s1字符串的时候,如果在拼接之前没有给s1分配足够的空间,那么s1的数据就会溢出到s2所在的空间,导致s2中原有的数据被破坏。而SDS在进行修改操作时,会先检查空间是否够用,如果不够用会自动扩展,以解决缓冲区溢出的问题。
内存重分配次数
对于C字符串而言,当对字符串进行拼接操作时,需要先通过内存重分配来扩展其底层的数组空间大小,如果不扩展,很可能就会出现缓冲区溢出,当金星字符串缩短时,也需要进行内存重分配,进行释放那部分不用的内存,如果忘了这步,会产生内存泄漏。可以看出,不管是字符串拼接,还是字符串缩短,都要执行内存重分配。
SDS就不一样了,SDS在进行字符串拼接的时候,采用空间预分配,举个例子进行讲解,如果我们有s1="hello"和s2=“sds”,两个字符串,以s1为研究对象,s1的len为5,free为0,当s2拼接到s1时,进行内存重分配,s1的len变成了8,free也变为8,这就是空间预分配,当你下次进行拼接"world"字符串的时候,由于free的空间够用,就可以直接分配了,而不用再次内存重分配。当我们进行缩短字符串的时候,SDS采用惰性空间释放,如果我们有一个字符串为s=“hellosds”,其len为8,free为0,当我们把sds进行截取掉的时候,其len变为了5,free变为了3,也就是这三个空间并没有通过内存重分配释放。这样就减少了内存重分配的次数,同时提高了效率,需要注意的是,free空间并不是无限制的大,free空间上限是1MB。
二进制安全
C字符串的编码是ASCII编码,在字符串的末尾是以\0结束,也就是空字符,所以在字符串中不能包含空字符,要不然会让程序误以为结束,这也限制了C字符串只能保存文本数据,不能保存图片,音频,视频等二进制数据。
SDS不仅可以保存文本数据,还可以以二进制方式把其他数据存储到buf数组中的,程序不会对数据做任何限制,写入什么样,读取就是什么样,这样不仅可以存储文本,而且可以存储任意格式的二进制数据。
SDS兼容部分C字符串函数
SDS虽然是二进制安全的,但是他同样遵循C字符串以空字符串解为的惯例,所以SDS可以重用C字符串的一些函数。
SDS总结
SDS获取字符串长度的时间复杂度是O(1),C字符串是O(n)。
SDS不会造成缓冲区溢出,C字符串可能会。
SDS修改N次字符串长度最多执行N次内存重分配,C字符串是执行N次。
SDS可以保存文本数据和二进制数据,C字符串只能保存文本数据。
SDS可以使用部分<string.h>库函数,C字符串可以使用全部。
总之SDS在Redis中很强。
List底层
List的底层实现是压缩列表(ziplist)和链表(双向链表linkedlist)
当列表键(是指列表的value底层实现)包含的列表项比较少的时候,就用压缩列表来做列表的底层实现
压缩列表实现
zlbytes
占4字节,记录压缩列表占用的内存字节
zltail
占4字节,记录压缩列表尾到表头距离的字节
zllen
占2字节,记录压缩列表包含的节点数
entryx
占不定字节,压缩列表的各个节点(可以是字节数组和整数值)
zlend
占1字节,标记压缩列表的表尾
压缩列表很像数组,可以简单理解为把数组进行了封装,里面包含了,一些属性,像zlend属性等
链表实现
下面给出每一个节点的底层实现
typedef struct listNode {struct listNode *prev;struct ListNode *next;void *value;// 保存节点的值
}listNode;
而list的底层就是多个链表节点连在一起的,另外,list还提供了一些固有的属性,方便操作,下面给出list的简单底层实现
typedef struct list {listNode *head;// 该指针指向链表的表头listNode *tail;// 该指针指向链表的表尾unsigned long len;// 记录链表的长度void *(*dup) (void *ptr);void (*free) (void *ptr);void (*match) (void *ptr,void *key);
}list;
list在操作链表的表头表尾和长度时,时间复杂度是O(1),list中还封装了有dup、free和match函数,分别用于复制链表节点所保存的值,释放链表节点所保存的值,对比链表节点所保存的值和另一个输入值是否相等。
列表常用操作
lpush
从列表的左侧添加元素
rpush
从列表的右侧添加元素
lpop
从列表的左侧弹出一个元素,并返回该元素的值
rpop
从列表的右侧弹出一个元素,并返回该元素的值
lindex
返回指定索引的值
llen
返回列表的长度
lrem
删除包含给定元素的节点
Hash底层
Hash的底层是ziplist和hashtable,上面已经说了压缩列表,这里着重讲解HashTable。
压缩列表
当数据少的时候用压缩列表,存储的时候是成对(键值对)存储的,意思是占用一个压缩列表的连续两个位置,而不是用两个压缩列表存。
HashTable
哈希表里保存了一个hash表数组、hash表的大小、hash表大小掩码,用于计算索引值、hash表的节点数
type struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
}dictht;
table是一个hash表数组,该数组里的每个元素都保存着一个指针,保存键值对。 当同一个key对应多个键时,通过链地址法解决冲突(详情可以看面试官问你什么是Hash表),当数据多的时候,用跳跃表来实现,跳跃表基本可以和红黑树相媲美。
Hash常用操作
hsest
添加新节点
hget
返回节点的值
hexists
查找指定键
hdel
删除指定键
hlen
返回键值对数
hgetall
返回所有键和值
Set底层
底层是整数集合(intset)和hashtable
整数集合
typedef struct intset {uint32_t encoding;uint32_t length;int8_t contents[];// 这里保存的int类型,会根据存储数据而改变,可能是int_8、int_16、int_32等
}intset;
其底层是一个数组,该数组中的元素不可重复。
Set常用操作
sadd
添加新元素到集合
scard
返回集合中的元素个数
sismember
查找指定元素
smembers
返回集合元素
srandmember
随机返回一个键
spop
随机取出一个键返回,并删除
srem
删除指定键
Zset底层
底层是ziplisst和skiplist,即是压缩列表和跳跃表
zset的特点就是可以通过分值排序。
Zset常用操作
zadd
插入成员和分值
zcard
获取元素数量
zcount
统计分值在范围内的元素数
zrange
从头遍历,返回给定索引范围内的元素
zrevrange
从尾遍历,返回给定索引范围内的元素
zrank
从头遍历,返回给定元素的排名
zrevrank
从尾遍历返回给定元素的排名
zrem
删除所有给定元素的节点以及分值节点
zscore
获取给定元素的分值
学习了Redis的五大数据类型,其底层实现你了解吗?相关推荐
- redis的zset的底层实现_Redis(三)--- Redis的五大数据类型的底层实现
1.简介 Redis的五大数据类型也称五大数据对象:前面介绍过6大数据结构,Redis并没有直接使用这些结构来实现键值对数据库,而是使用这些结构构建了一个对象系统redisObject:这个对象系统包 ...
- Redis中五大数据结构的底层实现
来自:DBAplus社群 作者介绍 田兆壮,新炬网络开发工程师.具备扎实的Java.Scala开发经验,熟练使用Python和Shell等脚本语言:具备前后端开发能力,熟练使用关系型数据库和非关系型数 ...
- NoSQL(1)之 Redis的五大数据类型使用方法的详细介绍
Redis的五大数据类型也称五大数据对象:了解过6大数据结构,Redis并没有直接使用这些结构来实现键值对数据库,而是使用这些结构构建了一个对象系统redisObject:这个对象系统包含了五大数据对 ...
- redis之五大数据类型
redis之五大数据类型 redis redis的两种链接方式 简单链接 1234 import redisconn = redis.Redis(host='10.0.0.200',port=6379 ...
- 解析Redis操作五大数据类型常用命令
摘要:分享经常用到一些命令和使用场景总结,以及对Redis中五大数据类型如何使用cmd命令行的形式进行操作的方法. 本文分享自华为云社区<Redis操作五大数据类型常用命令解析>,作者:灰 ...
- 配置文件存int类型_Redis详解(五)------ redis的五大数据类型实现原理
> 本系列教程持续更新,可以微信搜索「 IT可乐 」第一时间阅读.回复<电子书>有我为大家特别筛选的书籍资料 前面两篇博客,第一篇介绍了五大数据类型的基本用法,第二篇介绍了Redis ...
- Redis常用五大数据类型
1.String(字符串) string类型是二进制安全的.意思是redis的string可以包含任何数据.比如jpg图片或者序列化的对象 . string类型是Redis最基本的数据类型,一个red ...
- Redis的五大数据类型
1.String(字符串) String是Redis最基本的类型,一个Key对应一个Value. String类型是二进制安全的,意思是Redis的String可以包含任何数据,比如jpg图片或者序列 ...
- redis srandmember_Redis五大数据类型使用场景
Redis是一种基于键值对的NoSQL数据库,它的值主要由string(字符串),hash(哈希),list(列表),set(集合),zset(有序集合)五种基本数据结构构成,除此之外还支持一些其他的 ...
最新文章
- 【数据结构与算法】之深入解析“贪心算法“的原理解析和算法实现
- Visual Studio 常用快捷键 (二)
- 虚拟技术必须解决的问题_VR/3D虚拟实验室亮相重庆市初中物理青年教师优质课大赛...
- Google惊人研究:一组图片,就能强迫神经网络执行其他任务
- Scala 专题指南
- dcopserver出错解决办法
- 算法——最好理解的动态规划之01背包详解(看完这篇再不敢说自己不知道01背包算法!!!)
- 基于Microhard P900无人机PIX飞控远距离数传解决方案
- Java笔记17:JAVA常用函数
- 何钦铭.c语言程序设计,《C语言程序设计》 - 何钦铭
- php 58房源采集,如何用火车采集器采集二手房数据
- 100层高的大楼,丢玻璃球的问题
- 在Windows上安装Elasticsearch v5.4.2
- 绘制半长轴和半短轴分别为a,b的椭圆
- 读写锁 -- ReentrantReadWriteLock
- LVM-HOWTO/学习笔记(五)
- 【场景化解决方案】慧致造ERP,为企业提供生产全流程数字化管理
- Linux 挂载(NFS)
- 13图形光栅化——区域填充(种子填充)+多边形扫描转换(扫描线算法)
- 2013年第4季度MP3品牌网络知名度排名
热门文章
- GPUImage组合滤镜
- Flex4学习笔记(二)--语法相关
- Reids Lua 模糊查询所有key 及 相对应的集合总数
- HTTP性能测试工具wrk安装及使用
- SDF(Signed-distance-field: 有向距离场)(12): Shadow And AO(WebGL实现)增强立体感
- 关于svn的安装配置开启服务过程和 eclipse安装SVN插件的方法
- java 多线程 day06 threadLocal
- 动态规划经典题之石子合并
- Windows Embedded Standard 7 剪裁随笔
- JAVA设计模式-委派模式(Delegate)