redis五种基础数据结构底层汇总

redis写在了简历里,所以基础的东西还是要会了。

redis有五种基础数据结构:string、hash、list、set、zet。具体设计和为啥要这样设计呢?

string

虽然redis是C语言写的,但是并没有直接用C语言的字符数组来当自己的string。而是自己设计了一种SDS(Simple Dynamic String)的类型。

SDS定义如下

struct sdshdr {
    int len;//len:存储字符串的实际长度
    int free;//free:存储剩余(空闲)的空间
    char buf[];//buf[]:存储实际数据
};

SDS不仅定义字段和C语言字符数组不同,处理空字符’\0’也不同。SDS结尾也以空字符结尾,但是不计算字符串长度,SDS添加空字符到尾部操作是redis自动完成的,对程序员透明。遵循这个的好处是可以直接调用部分C语言的函数库

SDS结构也包含了字节数组,但是不同的是,新增了两个字段。因此有些与C语言写的字符数组不同的效果。

  • 求字符串长度是常数级。SDS的len字段让时间复杂度降低成了O(1)。C语言的字符数组不记录长度第一个问题,导致长度只能遍历,时间复杂度是O(N);

  • 杜绝缓冲区溢出。free字段记录剩余的空间,拼接操作发现空间够,那就放心大胆做。C语言字符数组不记录长度导致的第二个问题就是缓冲区溢出,一旦我们调用了拼接函数,而内存不够,就会产生缓冲区溢出的情况;

  • 二进制安全。SDS的len字段可以指示具体数据长度判断字符串是否结束。C语言当读取到“\0”,就认为已经读到结尾,后面即使有字符也不会读取。

C语言字符数组不记录长度导致的第三个问题就会导致每次增长或缩短C语言字符数组需要进行内存重分配(C语言字符数组底层是N+1个字符长的数组),内存重分配又导致两个问题

  • 缓冲区溢出。拼接操作如果内存不重分配足够内存空间,产生缓冲区溢出
  • 内存泄漏。截断操作如果内存不重分配释放掉不要内存空间,就会长沙内存泄漏

redis对以上两者问题进行了两个优化,核心目的是减少内存重分配次数

  • 空间预分配:当对字符串进行拼接操作的时候,Redis多分配一点剩余空间即未使用空间,下次如果发现空间足够分配就会直接使用未使用空间;
  • 惰性空间释放:当我们做了字符串缩减的操作,Redis并不会马上回收空间,因为你可能即将又要做字符串的拼接操作。

SDS动态字符串的当然可以扩容,机制类似Java的ArrayList。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是字符串最大长度为 512M。

区别
获取长度复杂度
安全无缓冲区溢出
redis的API安全
C语言串的API不安全溢出问题
保存数据区别
redis可以保存二进制数据或文本
C语言串仅能文本数据
修改长度内存重分配
redis长度为N至多需要N次
C语言串长度为N必然需要N次
hash(字典)

redis 的字典底层是一个哈希表,它是无序字典。内部实现结构和 Java 的 HashMap 一致的,同样的数组 + 链表二维结构,产生hash冲突使用拉链法解决。只不过redis的hash的value只能是string。

在说明redis的字典前,需要先说明redis的哈希表、哈希表的节点。最后才解释字典如何实现。

redis哈希表定义如下

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

redis哈希表 Node结构 dictEntry如下

type struct dictEntry{
 void *key;//键
 union{
 	void *val;
 	uint64_tu64;
 	int 64_ts64;
 }v;//值
 struct dictEntry *next;//下一个哈希表Node
} dictEntry;

Node中键值对中的值,可以是指针、是uint64_tu64整数、是64_ts64整数。

终于可以说到字典了

typedef struct dict{
	dictType *type;//类型特点函数
	void *private;//私有数据
	dictht ht[2];//哈希表,两个原因是一个平时用一个仅在rehash用
	int trehashidx;//rehash索引
}

其实上面的结构没啥大用。redis的字典结构面试只要把哈希算法、哈希冲突如何解决、渐进式rehash说了就基本OK了。

  • 哈希算法:和Java的HashMap一致,key的hash值 & sizemask,就是普通取模的高效位运算版。具体是MurmurHash算法。
  • 哈希冲突:和JDK7中的HashMap一致,拉链法采用的是头插法。因为dictEntry没有指向链表为的指针,若尾插只能遍历,用头插O(1)快多了,redis的单线程没有HashMap头插并发成环的问题
  • 渐进式rehash:redis会根据字典内的节点数量动态扩容或收缩。渐进式rehash是指在 rehash 的同时,保留新旧两个 hash 结构,查询时会同时使用两个 hash 结构,然后在后续中慢慢地地将旧 hash 一点点迁移到新的 hash 结构中。(有点类似concurrentHashMap,当然没有并发问题就不用搞那么复杂了)
list

Redis 的列表相当于 Java 里的 LinkedList,注意它是链表。意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)。

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);//节点对比函数
}

具体的listNode节点

typedef strcut listNode{
    struct listNode *prev;//前置节点
    struct listNode *next;//后置节点
    void *value;//节点值
}

从定义可以发现list底层是双向链表。因为*prev和*next都指向null,所以是无环的链表。深入再底层可以发现Redis 底层存储的还不是一个简单linkedlist,而是称之为快速链表 quicklist 的一个结构。面试答出上面我觉得就足够了。

首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是 ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成 quicklist。因为普通的链表需要的附加指针空间太大,会比较浪费空间,而且会加重内存的碎片化。比如这个列表里存的只是 int 类型的数据,结构上还需要两个额外的指针 prevnext 。所以 Redis 将链表和 ziplist 结合起来组成了 quicklist。也就是将多个 ziplist 使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

set

redis 的集合相当于 Java 里面的 HashSet,它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值NULL

待续

zset

redis是有序的set,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部实现用的是一种叫做「跳跃列表」的数据结构。

redis一般用zskiplist组织

typedef struct zskiplist{
	structz skiplistNode *header,*tail;
    unsigned long length;//节点数
    int level;//层数最大的节点的层数
}zskiplist;

skiplistNode节点定义如下

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

节点所在哪一层 1 至 32 之间的随机数随机生成的。跳表查询是从顶层自顶向下查找,类似二分。跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针(注:可以理解为维护了多条路径),从而达到快速访问节点的目的。

跳表redis用途

  • 实现有序集合键
  • 集群节点中用作内部数据结构

你可能感兴趣的:(总结,redis,数据结构)