Redis底层数据结构

简单动态字符串

什么是SDS?

Redis没有直接使用C语言的字符串,而是自己创建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型, 以此来表示Redis中的字符串。

SDS的数据结构?

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

SDS使用len属性的值而不是空字符来判断字符串是否结束。

为什么Redis要使用SDS而不是C字符串?

1.常数复杂度获取字符串长度。
SDS记录了所存字符串的长度,而C获取字符串长度需要遍历整个字符串,前者将复杂度从O(N)降到了O(1)。

2.杜绝缓存区溢出。
C字符串在拼接字符串时不会先检查内存的空间是否足够,而SDS内部的字符串拼接函数在拼接字符串前会检查空间是否足够,如果不够,则先扩展SDS空间,然后再执行拼接操作。

避免频繁地分配空间,SDS通过未使用空间解除了字符串长度和底层数组长度之间的关系,实现了空间预分配惰性空间释放两种优化策略。

  • 空间预分配:在对SDS进行空间扩展的时候,程度不仅会为SDS分配修改所需要的必须空间,还会分配额外的未使用空间。具体分配策略:len大小小于1M时,分配相同大小的free空间,此时len和free属性值相同;当len大于等于1M时,分配1M未使用空间。
  • 惰性空间释放:用于SDS字符串缩短操作。当需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,未来使用。

3.二进制安全。
C字符串的除了末尾外,不能包含空字符,否则会被认为是字符串结尾,这限制了C字符串只能保存文本数据,不能保存二进制数据。而SDS API会以处理二进制的方式来处理SDS存放在buf数组里的数据。
4.兼容部分C字符串函数
为了是能够让SDS可以重用<string.h>库定义的函数。

在这里插入图片描述

未使用空间在SDS中的作用?

SDS在字符数组buf的字符串末尾分配了额外的1字节空间,这样做的好处是SDS可以直接重用一部分C字符串函数库。但是不会算作字符串的长度


链表

Redis的链表的数据结构:

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;
  • dup函数用于复制链表节点所保存的值
  • free函数用于释放链表节点所保存的值
  • match函数则用于对比链表节点所保存的值和另一个输入值是否相等
    其中,链表的节点是listNode类型
typedef struct listNode {
	//前置节点
	struct listNode *prev;
	
	//后置节点
	struct listNode *next;

	//节点的值
	void *value;
} listNode;
  • 每个节点都有指向一个前置节点和后置节点,因此Redis的链表是个双端链表
  • 链表表头节点的前置节点和表尾节点的后置节点都指向NULL,所以Redis链表是个无环链表。
  • 链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。具体是如何办到的?
    在这里插入图片描述

字典

Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,每个哈希表节点保存了字典中的一个键值对。
Redis中的字典定义:

typedef struct dict {
	//类型特定函数
	dictType * type;
	
	//私有数据
	void *privdata;
	
	//哈希表
	dictht ht[2];
	
	//rehash索引,当rehash不在进行时,值为-1
	int trehashidx;
}  dict;
  • type属性和 privdata属性针对不同类型的键值对,为创建多态字典而设置的
  • type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。
  • privdata属性保存了需要传给那些特定类型函数的可选参数。
  • 字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。
  • rehashidx记录了rehash的进度,如果目前没有在进行rehash,那么它的值为-1.
    其中,哈希表定义:
typedef struct dictht {
	//哈希表数组
	dictEntry **table;
	
	//哈希表大小
	unsigned long size;

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

哈希表节点定义:

typedef struct dictEntry {
	//键
	void *key;
	
	//值
	union {
		void *val;
		uint64_tu64;
		int64_ts64;
	} v;
	struct dictEntry *next;
} dictEntry;
  • key保存键值对中对键
  • v保存键值对中的值,其中键值对的值可以是一个指针,或者是一个uint64_t整数,或者是一个int64_t整数。
  • next属性指向另一个哈希表节点的指针,用来解决键冲突
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值