Redis底层数据结构

 

 

 

简单动态字符串

都知道redis是通过c语言来编写的,但是c语言里面的字符串修改,存储等有诸多问题,不符合redis的设计思路,所以作者就自己定义了一种简单动态字符串简称SDS.

SDS的定义

SDS遵循了C字符串以空格字符结尾的惯例,保存空字符的1字节空间,但是不计算在SDS的len属性里面,并且为空字符串分配额外的1字节的空间,以及添加空字符到字符串末尾等操作,都是有SDS函数完成的,对开发者了来说是透明的,不用关心.

1个字符的\0,不算入字符串的len中,也不做为redis的结束符,只是为了兼容C语言而设计的.
C语言不记录自身长度,所以每次查看字符串长度都需要O(n)的时间复杂度,而SDS则为len直接可以看到,所以复杂度为O(1).

 

C语言字符串的缺点

C语言记录自身的长度,所以对于一个包含了N个字符的C字符串来说,C字符串底层实现总是一个N+1个字符长的数组.
所以C语言中字符串的增长或者缩短,都需要进行内存重新分配.

  • 如果程序执行的是增长字符串,比如a = a+“bbb”这样的操作,那么在执行这个操作之前,程序先要通过内存重新分配来扩展底层数组的空间大小,如果忘记这一步,就会–缓冲区溢出
  • 如果程序执行的是减少字符串,比如a= a.trim(), 那么在执行这个操作之后,程序需要通过内存重新分配释放字符串不再使用的那部分空间,如果忘记了这一步就会–内存泄漏

如果修改字符串不太长出现,那么每次修改都执行一次内存重新分配是OK了,但是redis这种数据库修改,删除是很常见的就不太适合了.

SDS就C语言缺点优化

内存预分配

 

字符串长度小于1MB则分配字符串2倍的空间.如果字符串长度大于等于1MB,则内存多分配1MB的空间.

分配内存步骤:如果字符串a=“aaa1111111”(长度为10,按照上面的规则,实际占用空间为21),现在修改a=”aaa1111111bbb"多加了3个字b,那么由于free=10,所以不需要在分配内存了,直接存入就OK了.

修改字符串为增加字符串时,内存空间只有在不够用的时候才重新分配,如果足够,则一直都不需要重新分配.

 

惰性空间释放


上面说了内存预分配,那么当字符串减少时呢?
还是a=“aaa1111111”(实际占用21个字符,len=10,free=10,1个\0字符),现在将a中的1移除掉,a=“aaa”了,那么a现在的空间是多少呢? 答案还是(len=3,free=17,1个\0字符).
就是说redis在减少字符串的时候,并不会重新分配内存,而是将空出来的一起做为备用空间.为下一次增加提供优化.

 

二进制安全


C字符串必须符合某种编码,并且出了字符串的末尾之外,字符串里买呢不能包括空字符,所以C字符串只能保存文本数据,不能保存图片,音频,视频等二进制数据.

SDS的API都是二进制安全的,所有SDS API都会以处理二进制的方式来处理SDS存放在buf数据里的数据.这样你存入什么数据,读取出来之后还是什么数据.

 

链表

链表的应用是非常广泛的,比如Reids的发布与订阅,满查询,监视器,保存多个客户端的状态信息等功能,都是使用链表来实现的.

链表结构:

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

//链表结构
typedef struct listNode{
   //前节点
	struct listNode *prev;
	//下一个节点
	struct listNode *next;
	//值
	void *value;
}

链表总结:

  • 双端:链表有prev和next指针,所以是一个双端链表
  • 无环: 表头节点的prev指针和表尾的next都指向了null
  • 多态: 链表节点使用void * 指针来保存节点值,所有链

字典

字典又称为符号表,关联表,或者映射(map),是一种保存键值对的抽象数据结构.
在字典中的每个key都是独一无二的,程序可以在字典中根据健查找与之关联的值.

redis字典数据结构源码:

typedef struct dict{

   //类型特定函数,比如计算hash值的函数,复制函数,键对比函数等.
	dictType *type;
	//私有数据
	void *privdata;
	//哈希表
	dictht ht[2];
	//当rehash不在进行时,值为-1
	in trehashidx;
}dict

typedef struct dictht{
//哈希表数组
   dictEntry **table;
   //哈希表大小(哈希表总的空间大小)
   unsigned long size;
   //哈希表大小烟吗,用于计算索引值
   //sizemask=size-1;
   unsigned long sizemask;
   //该哈希表已有节点的数量(哈希表中的真实的节点数量)
   unsigned long used;

}dictht


typedef struct dictEntry{

//键
 void *key;
 
 union{
 		void *val;
 		uint64_tu64;
 		int64_ts64;
 }v;
 //指向下一个哈希表节点,形成链表
 struct dicEntry *next;
} dicEntry

 

dict.ht[2]: 一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用.

hash键算法

//根据key计算hash值.
hash = dict->type->hashFunction(key);

//使用哈希表的sizemask属性和哈希值,计算出索引值
//根据情况不同,ht[x]可以是ht[0],或者ht[1]
index = hash& dict->ht[x].sizemask;

hash键冲突

 

rehash,渐进式rehash

跳跃表

跳跃表是一种有序的数据结构.跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点.大部分情况下,跳跃表的效率可以和平衡树差不多.

压缩列表

压缩列表(ziplist)是列表键和哈希键的底层实现之一.当一个列表键只包含少量列表项,并且列表项要么是小整数值,要么就是比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现.

结构

压缩列表是Redis为了节约内存而开发的,有一系列特殊编码的连续的内存块组成的顺序型数据结构.一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值.

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值