Redis 低级数据结构:一、介绍

Redis低级数据结构##

简单动态字符串###

一般的可变字符串用的都是这个,好处就是返回长度和剩余的长度都是O(1)的复杂度,另外自动提供扩容,不会溢出,另外因为free扩容后会自动预分配一些,阈值在不同情况下是不同的,最大多分配1MB的空间,因此减少了重分配次数,另外减少字符串长度时,除了清掉buf[]中的值,还会修改free的值,并不会主动释放内存空间(提供api释放,不会造成浪费)以便使用时不需要再次分配。

struct sdshdr {
    //当前长度
    int len;
    //剩余可用长度
    int free;
    char buf[];
}

链表###

listNode是一个双向链表对象,list是一个链表的工具类,一般用list。

 typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
 }

 typedef struct list{
    listNode *head;
    listNode *tail;
    unsigned long len;
    void *(*dup)(void *ptr);
    void (*free)(void *ptr);
    int (*match)(void *ptr,void *key);
 }

字典###

和java的hashmap结构差不多, size代表键值对的数量,sizemask是size-1,用来计算索引的,就是java的hash&length,这里的是hash=dict->type->hashFunction(key) 然后再计算hash&sizemask。然后每个dictEntry是一个单向链表,这里不会像hashmap一样,超过8个就转为rbtree。
然后是扩容过程,扩容标志是dict.trehashidx,-1代表正常,!-1代表扩容中。ht[0]代表正常使用的字典结构,当扩容时会渐进式hash把ht[0]里的值转移到ht[1]里面,此时所有对hash表的操作都在ht[1]里面,扩容完毕后,再把ht[0]释放,将ht[1]设为ht[0],最后重新建一个新的ht[1]

typedef struct dictEntry{
    void *key;
    union{
        void *val;
        uint64_t u64;
        int64_t s64;
    } v
    struct dictEntry *next;
}

typedef struct dictht{
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
}

typedef struct dict{
    dictType *type;
    void *privdata;
    dictht ht[2];
    int trehashidx;
}

跳跃表###

一种查询效率为O(logn)的数据结构,和平衡二叉树的效率类似。遍历时最慢的就是通过tail->backward 来遍历所有节点.快的就是通过跳跃表的层级来遍历,zskiplistNode.level[]代表了每个节点的层级,索引从小到大代表0级,1级...n级,zskiplistLevel.span代表的意思是两个节点的跨度(配合score来判断该怎么跳),zskiplist.level代表了跳跃表的最大层数。这样如果链表长度很多的时候,就通过level开辟了新的通道来查找元素。

typedef struct zskiplistNode{
    struct zskiplistNode *backward;
    double score;
    robj *obj;
    struct zskiplistLevel{
        struct zskiplistNode *forward;
        unsigned int span;
    } level[]
}

typedef struct zskiplist{
    struct zskiplistNode *header , *tail;
    unsigned long length;
    int level;
}

整数集合###

contents数组的值是有序的,而且不重复。encoding有三种,INTSET_ENC_INT16、INTSET_ENC_INT32、INTSET_ENC_INT64 ,就是contents里面数的范围不一样。int16是-32768
32767,int32是-2147483648 ~ 2147483647
另外就是升级的问题,整数集合里面的数据,会自动根据插入的值来升级,比如原来是int16的,然后插入一个int32,那么会遍历所有数据,将int16升为int32,没有降级过程。这样好处是在遍历的时候不会出现类型转换异常,而且也尽量节省了内存。
typedef struct intset{
    uint32_t encoding;
    uint32_t length;
    int8_t contents[];
}

压缩列表###

由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或整数值。

属性 类型 长度 用途
zlbytes uint32_t 4字节 记录整个压缩列表占用的内存字节数:内存重分配或计算zlend位置时使用
zltail uint32_t 4字节 记录尾节点地址和起始地址的内存偏移量,快速定位用的
zllen uint16_t 2字节 记录了节点数量,小于65535时 代表了节点数量, 等于65535时需要遍历才能知道具体数量
entryX 节点 不定 每个节点,长度由保存的内容决定
zlend uint8_t 1字节 特殊值 0xFF ,标记压缩列表的末端


图片参考的这篇博客!

节点的结构

属性 长度 用途
previous_entry_length 1或5字节 压缩列表中前一个列表的长度,前面长度小于254,则为1字节,否则为5字节,通过zltail和这个就能从后往前遍历所有节点了
encoding 1字节、2字节或5字节 记录content中保存的值的属性和类型,00、01、10是字节数组编码,11开头是整数编码
content 由encoding中保存 保存节点的值

连锁更新
previous_entry_length 一堆1字节的节点堆一块儿了,然后前面插入了一个266字节的节点,那么就会导致新插入节点后面的所有1字节的节点依次更新,删除同理。就是连锁更新。最坏时间复杂度为O(N^2) 但是形成条件苛刻,知道有这回事就行,不用太在意。

原文地址:https://www.cnblogs.com/june777/p/11910327.html