Redis数据类型与结构

Redis主要支持五种数据类型: String、List、Hash、Set、SortedSet。后续添加了 HyperLogLog类型。
字符串类型
        String类型 底层采用int、SDS(Simple Dynamic String)类型来存储字符串内容int用来存放整型数据,SDS存放字节/字符串和浮点型数据。在C的标准字符串结构下进行封装来提升基本操作的性能,同时也充分利用已有的C的标准库简化实现逻辑。可以在redis的源码中【sds.h】中看到sds的结构如下:
typedef char *sds;
redis3.2分支引入了五种sdshdr类型,目的是为了满足不同长度字符串可以使用不同大小的Header,从而节省内存。每次在创建一个sds时根据sds的实际长度判断应该选择什么类型的sdshdr,不同类型的sdshdr占用的内存空间不同。这样细分一下可以省去很多不必要的内存开销,下面是3.2版本的sdshdr定义:
`struct __attribute__ ((__packed__)) sdshdr8 {    //8表示字符串最大长度是2^8-1(长度为255);
uint8_t len;    //表示当前sds的长度(单位是字节);
uint8_t alloc;    //表示已为sds分配的内存大小(单位是字节);
unsigned char flags;    //用1Byte表示当前sdshdr类型,sdshdr有五种类型,因此至少需要3位来表示,高5位用不到所以都为0;
char buf[];    //sds实际存放的位置;
};`
说明:flags值的排列为000:sdshdr5/001:sdshdr8/010:sdshdr16/011:sdshdr32/100:sdshdr64。
内存布局:
列表类型
        列表类型(list)可以存储一个有序的字符串列表, 常用的操作是向列表两端添加元素或者获得列表的某一个片段。列表类型 内部使用双向链表实现。所以向 列表两端添加元素的时间复杂度为O(1), 获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是很快的。
        List类型在Redis3.2版本之后进行了优化。
3.2版本之前:
采用linkedList和zipList类型的数据结构。
list的key数量较多或者单个key的value较长时采用的linkedlist;
list的key的数据较少并且key的value长度较短时采用ziplist类型。
比较:linkedlist的查询效率较高但是占用空间较大。ziplist比较节省空间但是插入、删除等操作需要频繁申请和释放内存;
3.2版本之后:
采用quicklist类型的数据结构,quicklist是一个由ziplist组成的双向链表。相当于对linkedlist和ziplist做了一个结构上的整合。quicklist 中每个节点ziplist都能够存储多个数据元素,在源码中的文件为【quicklist.c】;
优缺点:
双向链表在链表两端进行push和pop操作,在插入节点上复杂度比较低,但是内存开销比较大;
ziplist存储在一段连续的内存上,所以存储效率很高,但是插入和删除都需要频繁申请和释放内存;
结构图:
HASH类型
        Map提供两种存储结构, 一种是hashtable、另一种是ziplist。数据量小的时候用ziplist。在redis中,哈希表分为三层,分别是:dict、dictEntry、dictht;源码为【dict.h】。
dictEntry
管理一个key-value,同时保留同一个桶中相邻元素的指针,用来维护哈希桶的内部链;
typedef struct dictEntry {
    void *key;
union {    //因为value有多种类型,所以value用了union来存储 void *val;
        uint64_t u64;
        int64_t s64;
        double d;
} v;
struct dictEntry *next;    //下一个节点的地址,用来处理碰撞,所有分配到同一索引的元素通过next指针链接起来形成链表key和v都可以保存多种类型的数据。
} dictEntry;
dictht
实现一个hash表会使用一个buckets(桶)存放dictEntry的地址,一般情况下通过hash(key)%len得到的值就是buckets的索引,这个值决定了我们要将此dictEntry节点放入buckets的哪个索引里,这个buckets实际上就是我们说的hash 表。dict.h的dictht结构中table存放的就是buckets的地址。
typedef struct dictht {
    dictEntry **table;    //buckets的地址;
    unsigned long size;    //buckets的大小,总保持为2^n;
    unsigned long sizemask;    //掩码,用来计算hash值对应的buckets索引;
    unsigned long used;    //当前dictht有多少个dictEntry节点;
} dictht;
dict
dictht实际上就是hash表的核心,但是只有一个dictht还不够,比如rehash、遍历hash等操作,所以redis定义了一个叫dict的结构以支持字典的各种操作,当dictht需要扩容/缩容时,用来管理dictht的迁移,以下是它的数据结构
typedef struct dict {
    dictType *type;    //dictType里存放的是一堆工具函数的函数指针;
    void *privdata;    //保存type中的某些函数需要作为参数的数据;
    dictht ht[2];    //两个dictht,ht[0]平时用,ht[1] rehash时用;
    long rehashidx;    //当前rehash到buckets的哪个索引,-1时表示非rehash状态;
    int iterators;    //安全迭代器的计数;
} dict;
比如我们要将一个数据存储到 hash 表中,那么会先通过 murmur 计算 key 对应的 hashcode ,然后根据 hashcode 取模得到 bucket 的位置,再插入到链表中。
集合类型
        Set在 底层数据结构以intset或者hashtable来存储。当set中只包含整数型的元素时,采用intset来存储,否则,采用hashtable存储,但是对于set来说,该 hashtable通过key来存储元素,所以value值为NULL。集合类型的常用操作是向集合中加入、删除元素、判断元素是否存在。由于集合类型在redis内部是使用的值为空的散列表(hash table),所以这些操作的时间复杂度都是O(1)。
有序集合类型
        在集合类型的基础上,有序集合类型为集合中的每个元素都关联了一个分数,这使得我们不仅可以完成插入、删除 和判断元素是否存在等集合类型支持的操作,还能获得分数最高(或最低)的前N个元素、获得指定分数范围内的元素等与分数有关的操作。 虽然集合中每个元素都是不同的,但是他们的分数却可以相同。zset类型的数据结构就比较复杂一点,内部是以ziplist或者skiplist+hashtable来实现,这里面最核心的一个结构就是skiplist,也就是跳跃表。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值