Redis的五种对象类型

基本概念

Redis是一个基于内存中的数据结构存储系统,可以用作数据库、缓存和消息中间件。Redis支持五种常见的对象类型:

  • 字符串(String)
  • 哈希(Hash)
  • 列表(List)
  • 集合(Set)
  • 有序集合(Zset)
对象类型与编码

Redis 使用对象来存储键和值,在Redis中,每个对象是由redisObject结构表示。redisObject结构主要包含三个属性:type、encoding 和 ptr。

typedef struct redisObject {
	//记录对象数据类型
	unsigned type:4;
	//对应的编码方式
	unsigned encoding:4;
	//对象的底层数据结构
	void *ptr;
} robj;

常说Redis采用哪种对象类型,是指对应的值采用哪种数据类型。
之所以用encoding属性来决定对象的底层数据结构,是为了实现同一种对象类型,支持不同的底层实现。这样做的好处是,可以根据具体的场景,选取不同的编码方式,使用不同的数据结构,可以提高redis数据库的灵活性和效率。

字符串对象

非常常用的一种对象类型,像上面提及的那样,它可以有int、raw和embstr三种编码方式。

当字符串对象保存值的长度是一个不超过long类型的整数值时,编码方式为int,底层数据结构直接是long类型;
当字符串保存值的长度是一个大于等于39字节的字符串时,对应的编码类型为raw,其底层数据结构为简单动态字符(SDS);
当字符串保存值的长度是一个小于39字节的字符串时,编码类型为embstr,底层数据就是embstr编码SDS;

SDS相比较C字符串的不同点

  • 常数复杂度获取字符串长度

SDS采用len属性来记录每个字符串的长度,因此,获得SDS字符串的长度时间复杂度为O(1)。

  • 杜绝缓存区溢出

在SDS进行字符串扩充时,首先会检查当前字符数组的长度是否足够。不够的话,要进行扩容操作。

  • 减少修改字符串是带来的内存分配次数

增长或者缩短字符串时,都需要进行内存重分配来进行操作,避免了内存泄漏和内存溢出问题。
空间预分配和惰性空间释放两种优化
空间预分配(增长字符串操作)
字节数组空间不足时,总能预留出来一部分空间,这样能减少连续执行字符串增长操作时的内存重分配次数。
a、当len小于1MB时,每次重分配同样大小的空闲空间
b、当len大于1MB时,每次重分配会多分配1MB的空闲空间
惰性空间释放(缩短字符串)
并不会在字符串缩短时,立即使用内存重分配来回收内存空间,而是用free记录下来,等待将来扩容时在去使用这部分内存。

  • 二进制安全

SDS做到了二进制安全,使得Redis可以保存二进制数据

列表对象

类标编码的对象可以是linkedlist或者ziplist,对应的数据结构分别是链表和压缩链表。

编码方式
当字符串长度小于64字节时,切元素个数小于512个时,列表采用ziplist编码,否则采用linkedlist编码。

对应节点数据结构

typedef struct listNode {
	//前置节点
	struct listNode *prev;
	//后置节点
	struct listNode *next;
	//当前节点对应的值
	void *value;
} listNode;

列表数据结构

typedef struct list {
	listNode *head;
	listNode *tail;
	unsigned long len;
	//节点复制函数
	void *(*dup)(void *ptr);
	//节点释放函数
	void *(*free)(void *ptr);
	//节点对比函数
	int (*match)(void *ptr, void *key);

链表特点

  • 双端:*prev, *tail
  • 无环:prev => NULL;表尾节点next => NULL
  • 获取表头指针和表尾指针的复杂度为O(1)
  • 带有链表长度计数器
  • 多态

压缩列表

列表键和哈希键的底层实现之一。它是由一系列特殊编码的连续内存块组成的顺序性数据结构。

哈希对象

编码方式:ziplist 和 hashtable

hash-ziplist
将每次保存的键值值压入压缩列表的表尾,先键后值。

hash-hashtable
使用字典作为底层实现。字典适用于保存键值对的一种数据结构。

哈希表

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

table属性是一个数组,每个元素对应指向dictEntry结构的指针。

typedef struct dictEntry {
	void *key;
	union {
		void *val;
		unit64_t u64;
		nit64_t s64;
	} v;
	struct dictEntry *next;
} dictEntry;

字典

typedef struct dict {
	dictType *type;
	void *privdata;
	//哈希表
	//使用ht[0]哈希表,ht[1]用于在进行rehash时使用
	dictht hrt[2];
	//rehash索引
	int rehashidx;
}

rehash

使得负载因子 ht[0]).used / ht[0]).size 维持在一个合理范围,在哈希表元素过多或者过少时,需要对hash表进行相应的扩展与收缩。
步骤
为 ht[1] 分配空间
1、扩展:大小为第一个大于 ht[0]).used*2的2n
2、收缩:大小为第一个大于 ht[0]).used的2n
3、将 ht[0] 的元素复制rehash到 ht[1] 中
4、将 ht[0] 重命名为 ht[1],方便下一次rehash

渐进式hash
一次迁移一个桶上的所有数据,将原本集中的操作分散到每个添加,删除,查找和更新上,使用到了分治算法。

集合对象

set-intset
对应编码方式:intset或者hashtable(元素个数>=512)
整数集合的数据结构

typedef struct intset {
	uint32_t encoding;
	uint32_t length;
	int8_t contents[];
} intset;

整数集合的类型升级
1、扩展整数集合底层数组空间的大小;
2、将底层数组现有的所有元素都转移为新元素相同的类型,并维持底层数组的有序性;
3、将新元素添加到底层数组里面。
整数集合不支持类型降级

set-hashtable
使用字典作为其底层实现

有序集合

编码方式:ziplist(元素个数 <= 128 && 长度 <= 64字节) 和 skiplist(跳跃表)

zset-ziplist
使用压缩列表作为底层实现,集合中的元素会根据分值的大小从小到大排列。

zset-skiplist
skiplist编码的有序集合对象使用zset结构作为其底层实现

typedef struct zset {
	zskiplist *zsl;
	dict *dict;
}

跳跃表数据结构
跳跃表节点zskiplistNode

typedef strcut zskiplist {
	//后退指针
	struct zskiplistNode *backward;
	//分值
	double score;
	//成员对象
	robj *obj;
	//层级
	struct zskiplistLevel {
	//前进指针
		struct zskiplistNode *foreward;
		//跨度
		unsigned int span;
	} level[];
} zskiplistNode;

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值