redis adlist

ddatsh

db #redis redis

https://www.xiebruce.top/1820.html

A generic doubly linked list (adlist)

发展历程

3.2 前

列表数据类型通过双向链表(linked list)或压缩列表(ziplist)实现,根据使用场景选择最优的存储方式

双向链表支持大量元素的存储,但每个元素都需要额外的指针空间,存储小元素时会造成内存浪费

压缩列表则是一个紧凑的数据结构,节省内存,但不适合存储大量元素,插入和删除操作可能需要重新分配和复制整个数据结构,导致性能问题

3.2

链表的附加空间相对太高,prev 和 next 指针就要占去 16 个字节 (64bit 系统的指针是 8 个字节),另外每个节点的内存都是单独分配,会加剧内存的碎片化,影响内存管理效率

对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist

结合了双向链表和压缩列表的数据结构,由多个压缩列表节点组成的双向链表。既能够保持较高的内存效率,又能够提供良好的操作性能,特别是在列表元素非常多或者非常少的情况下

7.0

quicklist 底层实现发生重要变化,将底层存储结构从 ziplist 替换为 listpack

为解决 ziplist 在处理某些操作时的性能问题,同时进一步优化内存使用效率。listpack 是一种更为紧凑和高效的序列化数据结构,以改进存储效率和操作性能

结构

链表,链表节点和迭代器,以及链表相关方法

listNode 结构体:链表节点

list 结构体:整个双向链表

listNode

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

list

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

sizeof(*list) ,64位os,5 个指针一个 long 类型 = 6 * 8=48

结构特性

源码

listCreate

list *listCreate(void){
	// 声明 list结构体的指针list
    struct list *list;
	// list 结构体分配内存
    if ((list = zmalloc(sizeof(*list))) == NULL)
        return NULL;
    // 初始化链表头尾指针为 NULL,链表长度len=0
    list->head = list->tail = NULL;
    list->len = 0;
    // 复制、释放和匹配函数为 NULL
    list->dup = NULL;
    list->free = NULL;
    list->match = NULL;
    return list;
}

listRelease

void listRelease(list *list){
    if (!list) // 避免了在空指针上执行操作,这可能会导致未定义行为或程序崩溃。
        return;
    // listEmpty释放链表中的所有节点,逐个释放每个节点所占用的内存
    listEmpty(list);
    // zfree释放list结构体本身所占用的内存
    zfree(list);
}

listEmpty

void listEmpty(list *list)  {  
    // 获取链表的长度  
    unsigned long len;  
    // 定义当前节点和下一个节点的指针  
    listNode *current, *next;  
      
    // 将当前节点指针指向链表的头节点  
    current = list->head;  
      
    // 将链表长度赋值给len变量,方便后续遍历  
    len = list->len;  
      
    // 循环遍历链表,直到所有节点都被处理  
    while(len--) {  
        // 将下一个节点指针指向当前节点的下一个节点  
        next = current->next;  
          
        // 如果链表定义了释放节点值的函数,则调用该函数释放当前节点的值  
        if (list->free) list->free(current->value);  
          
        // 释放当前节点所占用的内存  
        zfree(current);  
          
        // 将当前节点指针指向下一个节点,准备处理下一个节点  
        current = next;  
    }  
      
    // 将链表的头节点和尾节点指针置为NULL,表示链表为空  
    list->head = list->tail = NULL;  
      
    // 将链表长度置为0,表示链表没有节点  
    list->len = 0;  
}

listAddNodeHead

// 接受一个指向 list 结构的指针和一个 void 类型的指针作为参数
// 返回一个指向 list 结构的指针
list *listAddNodeHead(list *list, void *value)  {  
    // 指向 listNode 结构的指针 node
    listNode *node;
  
    // 为 listNode 分配内存,并将返回的地址赋值给 node
    if ((node = zmalloc(sizeof(*node))) == NULL)  
        // 如果内存分配失败,则返回 NULL  
        return NULL;  
  
    // 将传入的 value 赋值给新节点的 value 字段
    node->value = value;  
  
    // 调用 listLinkNodeHead 函数,将新节点添加到链表的头部  
    listLinkNodeHead(list, node);  
  
    // 返回更新后的链表指针
    return list;  
} 

listAddNodeTail

只有 listLinkNodeTail(list, node); 不同

listLinkNodeHead

// 将一个节点链接到链表的头部  
void listLinkNodeHead(list* list, listNode *node) {  
    // 如果链表为空(即长度为0)  
    if (list->len == 0) {  
        // 将新节点设置为链表的头节点和尾节点  
        list->head = list->tail = node;  
        // 因为链表只有一个节点,所以该节点的prev和next都指向NULL  
        node->prev = node->next = NULL;  
    } else {  
        // 如果链表不为空,新节点的prev指向NULL(因为它是头部节点)  
        node->prev = NULL;  
        // 将新节点的next指向当前的头节点  
        node->next = list->head;  
        // 将当前头节点的prev指向新节点,形成双向链表的链接  
        list->head->prev = node;  
        // 将链表的头节点更新为新节点  
        list->head = node;  
    }  
    // 无论链表是否为空,都将链表的长度加1  
    list->len++;  
}

链表为空:

链表不为空:

链表长度更新:

listDelNode

// 从列表中删除一个节点,并释放相关内存  
void listDelNode(list *list, listNode *node)  {  
    // listUnlinkNode从列表中解除该节点的链接  
    listUnlinkNode(list, node);  
    // 列表的free字段不为空(即存在一个用于释放节点值的回调函数)  
    if (list->free)   
        // 调用该回调函数,释放节点中存储的值所占用的内存  
        list->free(node->value);  
      
    // zfree释放节点本身所占用的内存  
    zfree(node);  
}

listUnlinkNode

// 从双向链表中移除一个节点,但不释放其内存  
void listUnlinkNode(list *list, listNode *node) {  
    // 如果该节点有前一个节点  
    if (node->prev) {  
        // 将前一个节点的next指针指向当前节点的下一个节点  
        node->prev->next = node->next;  
    } else {  
        // 否则,当前节点是头节点,将列表的头指针指向当前节点的下一个节点  
        list->head = node->next;  
    }  
      
    // 如果该节点有下一个节点  
    if (node->next) {  
        // 将下一个节点的prev指针指向当前节点的前一个节点  
        node->next->prev = node->prev;  
    } else {  
        // 否则,当前节点是尾节点,将列表的尾指针指向当前节点的前一个节点  
        list->tail = node->prev;  
    }  
      
    // 将当前节点的next和prev指针都设置为NULL,表示它已经从链表中移除  
    node->next = NULL;  
    node->prev = NULL;  
      
    // 减少列表的长度计数  
    list->len--;  
}

可以重新利用该节点,或者将其添加到其他列表中,而无需再次分配内存