1、Redis是什么?

Redis是一个数据库,不过与传统的数据库不同的是Redis数据库是工作在内存中,所以读写数据非常快,因此Redis广泛应用于缓存方面。

2、Redis的五种数据结构整理

简单动态字符串、list、字典、哈希表、跳跃表、压缩列表

简单动态字符串(Simple Dynamic String, SDS)

Redis没有直接使用C语言传统的字符串,而是使用自己构建的一个简单动态字符串(SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。

简单来说,SDS其实类似C语言的char*,但是它可以存储任意的二进制数据,不需要像C语言那样,使用'\0'标识字符串结束。而是采用一个len表示字符串的大小。

代码如下:

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

优点:

  1. 获取字符串长度的复杂度为O(1)。
  2. 杜绝缓冲区溢出。
  3. 二进制安全
  4. 兼容部分C语言字符串函数。

链表(不是循环链表)

代码如下:

节点底层结构

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

list底层结构:

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;

特性:

  1. 链表被广泛用于实现Redis的各种功能,比如订阅、发布等。
  2. 每个链表节点使用listNode来表示,每个节点都是有一个指向前置节点和后置节点的指针,所以Redis的链表实现是双端链表。
  3. 每个链表使用一个list结构表示,这个结构带有头结点指针和尾结点指针,以及链表长度等信息。
  4. 因为链表表头的头结点和链表表尾的尾结点都是指向NULL,所以Redis链表是无环链表。
  5. 通过为链表设置不同类型的特定函数,Redis的链表可以用于保存各种不同类型的值。

字典:

类似c++里面的map。

哈希表:

typedef struct dictht {
    // 哈希表数组
    dictEntry **table;
    // 哈希表大小
    unsigned long size;
    // 哈希表大小掩码,用于计算索引值
    // 总是等于size-1
    unsigned long sizemark;
    // 该哈希表已有节点的数量
    unsigned long used;
} dichht;

当字典或者哈希表作为底层实现时候,Redis使用murmurhash算法,该算法的好处是能够把有些的key映射到随机的地址上面(hash冲突的概率比较小),并且算法计算非常快。

如果hash冲突了,如何解决,?

使用链地址法解决问题。简单来说就是让hash冲突的key形成一个链表连接起来。

特性:

  1. 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。
  2. Redis中字典使用哈希表作为底层结构实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash的时候使用。
  3. Redis使用murmurhash计算hash值。
  4. 哈希表使用链地址法来解决冲突问题。

跳跃表:

首先看看单链表

Redis面试点 一_Redis

由上图可以看到,如果要找某个值,只能顺序遍历过去,时间复杂度在o(n)级别的,这个效率是非常低的。

而跳跃表就是说把这个单链表,变成多个单链表,每次走的步长都不一样,从而可以很容易的查找到数据的范围,加速查找。

跳跃节点

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

一般来说,跳跃表的层数越大,查询的速度就越快。

跳跃表节点

typedef struct zskiplist {
    // 表头节点和表尾节点
    struct zskiplistNode *header, *tail;
    // 表中节点的数量
    unsigned long length;
    // 表中层数最大的节点的层数
    int leval;
} zskiplist;

特性:

  1. 跳跃表本身是有序集合的底层实现之一。
  2. Redis的跳跃表是由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点。
  3. 每个跳跃表节点的层高都是1到32之间的随机数
  4. 跳跃表中的节点都是按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序。
  5. 跳跃表是一种实现起来简单,单层多指针的链表,它的查找效率很高,堪比优化过的二叉平衡树。

压缩列表

特性:节省内存