首页 数据库 mysql教程 【Redis】对通用双向链表实现的理解

【Redis】对通用双向链表实现的理解

Jun 07, 2016 pm 03:54 PM
redis 实现 理解 通用 链表

Redis实现的双向链表还是比较容易看得懂的,其实现的原理很经典, 代码很整洁清晰。 以下是对其源码注释的翻译及本人见解的部分说明,如有偏颇欢迎指正: /* adlist.h - 通用双向链表的实现*/#ifndef __ADLIST_H__#define __ADLIST_H__/* 目前的数据结构只使用

Redis实现的双向链表还是比较容易看得懂的,其实现的原理很经典, 代码很整洁清晰。

以下是对其源码注释的翻译及本人见解的部分说明,如有偏颇欢迎指正:

/* adlist.h - 通用双向链表的实现*/

#ifndef __ADLIST_H__
#define __ADLIST_H__

/* 目前的数据结构只使用了Node, List, and Iterator. */

/* list节点*/
typedef struct listNode {
    struct listNode *prev;        // 前向指针
    struct listNode *next;        // 后向指针
    void *value;                  // 当前节点值
} listNode;

/* list迭代器*/
typedef struct listIter {
    listNode *next;               // 节点指针
    int direction;                // 迭代方向 
} listIter;

/*链表结构*/
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;

/* 函数宏定义 */
#define listLength(l) ((l)->len)                       // 链表长度
#define listFirst(l) ((l)->head)                       // 链表头节点
#define listLast(l) ((l)->tail)                        // 链表末节点
#define listPrevNode(n) ((n)->prev)                    // 指定节点的前驱节点
#define listNextNode(n) ((n)->next)                    // 指定节点的后继节点
#define listNodeValue(n) ((n)->value)                  // 指定节点的值

/* 函数指针, 设置外部调用模块的自定义的方法 */
#define listSetDupMethod(l,m) ((l)->dup = (m))         // 复制链表
#define listSetFreeMethod(l,m) ((l)->free = (m))       // 释放链表
#define listSetMatchMethod(l,m) ((l)->match = (m))     // 匹配

/* 函数指针, 获取外部调用模块的自定义的方法 */
#define listGetDupMethod(l) ((l)->dup)                      // 获取复制的自定义方法
#define listGetFree(l) ((l)->free)                          // 获取释放的自定义方法
#define listGetMatchMethod(l) ((l)->match)                  // 获取匹配的自定义方法

/* 函数原型 */
list *listCreate(void);                                                               // 创建链表
void listRelease(list *list);                                                         // 释放链表
list *listAddNodeHead(list *list, void *value);                                       // 在表头添加节点
list *listAddNodeTail(list *list, void *value);                                       // 在表尾添加节点
list *listInsertNode(list *list, listNode *old_node, void *value, int after);         // 在指定位置之后添加节点
void listDelNode(list *list, listNode *node);                                         // 删除节点
listIter *listGetIterator(list *list, int direction);                                 // 获取列表迭代器
listNode *listNext(listIter *iter);                                                   // 获取下一个节点
void listReleaseIterator(listIter *iter);                                             // 释放列表迭代器
list *listDup(list *orig);                                                            // 复制链表
listNode *listSearchKey(list *list, void *key);                                       // 给定key查找节点
listNode *listIndex(list *list, long index);                                          // 给定index查找节点
void listRewind(list *list, listIter *li);                                            // 迭代器指针重新指向头节点
void listRewindTail(list *list, listIter *li);                                        // 迭代器指针重新指向末节点
void listRotate(list *list);                                                          // 链表翻转, 末节点移动成为头节点

/* 迭代器的迭代方向 */
#define AL_START_HEAD 0
#define AL_START_TAIL 1

#endif /* __ADLIST_H__ */
登录后复制
/* adlist.c - 通用双向链表的实现 */

#include <stdlib.h>
#include "adlist.h"
#include "zmalloc.h"

/* 创建新链表. 新建的链表可以用函数
* AlFreeList()来释放, 但调用此函数之前需要要应用手动释放对每个节点的私有值空间
*
* 出现错误则返回NULL,否则返回指向该list的指针*/
list *listCreate(void)
{
    struct list *list;

    if ((list = zmalloc(sizeof(*list))) == NULL)       // 用了在malloc之上封装的zmalloc来申请内存
        return NULL;
    list->head = list->tail = NULL;
    list->len = 0;
    list->dup = NULL;
    list->free = NULL;
    list->match = NULL;
    return list;
}

/* 释放链表,该方法不能失败.
*
 */
void listRelease(list *list)
{
    unsigned long len;
    listNode *current, *next;

    current = list->head;
    len = list->len;
    while(len--) {
        next = current->next;
        if (list->free) list->free(current->value);   // 每个节点指向的空间都会被释放
        zfree(current);       // zfree基于系统函数free上的封装                                
        current = next;
    }
    zfree(list);
}

/* 把包含指针指向的值的节点插入链表头部
*
* 如发生错误,将返回NULL并且不对链表进行任何操作,
*
* 如成功则返回该链表指针.*/
list *listAddNodeHead(list *list, void *value)
{
    listNode *node;
    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    if (list->len == 0) {
        list->head = list->tail = node;
        node->prev = node->next = NULL;
    } else {
        node->prev = NULL;
        node->next = list->head;
        list->head->prev = node;
        list->head = node;
    }
    list->len++;
    return list;
}

/* 把包含指针指向的值的节点插入链表尾部,

* 如发生错误,将返回NULL并且不对链表进行任何操作,
*
* 如成功则返回该链表指针.*/
list *listAddNodeTail(list *list, void *value)
{
    listNode *node;
    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    if (list->len == 0) {
        list->head = list->tail = node;
        node->prev = node->next = NULL;
    } else {
        node->prev = list->tail;
        node->next = NULL;
        list->tail->next = node;
        list->tail = node;
    }
    list->len++;
    return list;
}

/* 把新节点插入链表某个节点的前或后,
* 如发生错误,将返回NULL并且不对链表进行任何操作,
*
* 如成功则返回该链表指针.*/
list *listInsertNode(list *list, listNode *old_node, void *value, int after) {
    listNode *node;

    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    if (after) {                          // after!=0则表示节点插入的位置在旧节点之后,否则在其之前
        node->prev = old_node;
        node->next = old_node->next;
        if (list->tail == old_node) {
            list->tail = node;
        }
    } else {
        node->next = old_node;
        node->prev = old_node->prev;
        if (list->head == old_node) {
            list->head = node;
        }
    }
    if (node->prev != NULL) {
        node->prev->next = node;
    }
    if (node->next != NULL) {
        node->next->prev = node;
    }
    list->len++;
    return list;
}

/* 从链表中删除某个节点.
* 会调用底层函数把节点的空间释放.
*
* 该方法不能失败. */
void listDelNode(list *list, listNode *node)
{
    if (node->prev)
        node->prev->next = node->next;
    else
        list->head = node->next;
    if (node->next)
        node->next->prev = node->prev;
    else
        list->tail = node->prev;
    if (list->free) list->free(node->value);
    zfree(node);
    list->len--;
}

/* 获取迭代器对象&#39;iter&#39;. 在初始化之后
*每次调用listNext()都会返回链表的下一个元素.
*
* 该方法不能失败. */
listIter *listGetIterator(list *list, int direction)
{
    listIter *iter;

    if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;
    if (direction == AL_START_HEAD)
        iter->next = list->head;
    else
        iter->next = list->tail;
        iter->direction = direction;
    return iter;
}

/* 释放迭代器对象的空间 */
void listReleaseIterator(listIter *iter) {
    zfree(iter);
}

/* 将迭代器指针重新指向表头 */
void listRewind(list *list, listIter *li) {
    li->next = list->head;
    li->direction = AL_START_HEAD;
}

/* 将迭代器指针重新指向表尾 */

void listRewindTail(list *list, listIter *li){
    li->next = list->tail;
    li->direction = AL_START_TAIL;
}

/* 获取迭代器的下一个元素.
* 可以通过listDelNode()方法来删除当前返回的节点,但不能删除其他的节点。
*
* 如果成功则返回迭代器的下一个元素,否则返回NULL;
* 因此推荐以下这样使用:
*
* iter = listGetIterator(list,<direction>);
* while ((node = listNext(iter)) != NULL) {
* doSomethingWith(listNodeValue(node));
* }
*
* */
listNode *listNext(listIter *iter)
{
    listNode *current = iter->next;

    if (current != NULL) {
        if (iter->direction == AL_START_HEAD)
        iter->next = current->next;
    else
        iter->next = current->prev;
    }
    return current;
}

/* 复制整个链表. 
* 成功则返回复制的链表指针,否则返回NULL.
*
* 复制的方法通过listSetDupMethod()来指定,
* 如果没有指定dup方法则会完整拷贝原始节点的值.
*
* 原始链表不会给更改. */
list *listDup(list *orig)       // 有个疑问: 既然需要保持原始链表的不被修改,为什么不加const修饰?
{
    list *copy;
    listIter *iter;
    listNode *node;

    if ((copy = listCreate()) == NULL)
        return NULL;
    copy->dup = orig->dup;
    copy->free = orig->free;
    copy->match = orig->match;
    iter = listGetIterator(orig, AL_START_HEAD);
    while((node = listNext(iter)) != NULL) {
        void *value;

        if (copy->dup) {
            value = copy->dup(node->value);
            if (value == NULL) {
                listRelease(copy);
                listReleaseIterator(iter);
                return NULL;
            }
        } else
            value = node->value;
        if (listAddNodeTail(copy, value) == NULL) {
            listRelease(copy);
            listReleaseIterator(iter);
            return NULL;
        }
    }

    listReleaseIterator(iter);

    return copy;
}

/* 通过指定key来查找节点.
* 查找节点的匹配方法可以通过listSetMatchMethod()来指定. 
* 如果外部调用模块没有指定匹配方法, 则直接比较key值和链表中节点指针指向的值.
*
* 如果正常将返回第一个匹配的节点指针,如果找不到匹配元素则返回NULL. */
listNode *listSearchKey(list *list, void *key)
{
    listIter *iter;
    listNode *node;

    iter = listGetIterator(list, AL_START_HEAD);
    while((node = listNext(iter)) != NULL) {
        if (list->match) {                                       // 使用自定义的match方法
            if (list->match(node->value, key)) {
                listReleaseIterator(iter);
                return node;
            }
        } else {                                                     // 直接比较值
            if (key == node->value) {
                listReleaseIterator(iter);
                return node;
            }
        }
    }
    listReleaseIterator(iter);   // 释放iter对象
    return NULL;
}

/* 根据index来获取元素。
* 如果传入index为非负值,说明为正向迭代: 0为头节点,1为下一个节点,以此类推.
* 如果为负值,则说明为反向迭代: -1为尾节点, -2为倒数第二个节点, 以此类推
* 如果index越界则返回NULL. */
listNode *listIndex(list *list, long index) {
    listNode *n;

    if (index < 0) {
        index = (-index)-1;
        n = list->tail;
        while(index-- && n) n = n->prev;
    } else {
        n = list->head;
        while(index-- && n) n = n->next;
    }
    return n;
}

/* 翻转链表, 将尾节点插入到链表头. */
void listRotate(list *list) {
    listNode *tail = list->tail;

    if (listLength(list) <= 1) return;

    /* 将当前末节点从链表中摘除 */
    list->tail = tail->prev;
    list->tail->next = NULL;
    /* 将末节点插入链表头 */
    list->head->prev = tail;
    tail->prev = NULL;
    tail->next = list->head;
    list->head = tail;
}
登录后复制
有两点还需要继续了解:

1)既然源码中list空间的创建及销毁是通过zmalloc模块的zmalloc和zfree来完成, zmalloc又是怎么实现的呢?

2)很好奇这么多对象指针都没有const作为限制, 是什么原因可以省略了它呢?

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前 By 尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Windows11安装10.0.22000.100跳出0x80242008错误解决办法 Windows11安装10.0.22000.100跳出0x80242008错误解决办法 May 08, 2024 pm 03:50 PM

1、启动【开始】菜单,输入【cmd】,右键点击【命令提示符】,选择以【管理员身份】运行。2、依次输入下面命令(可小心复制贴上):SCconfigwuauservstart=auto,按回车SCconfigbitsstart=auto,按回车SCconfigcryptsvcstart=auto,按回车SCconfigtrustedinstallerstart=auto,按回车SCconfigwuauservtype=share,按回车netstopwuauserv,按回车netstopcryptS

Golang API缓存策略与优化 Golang API缓存策略与优化 May 07, 2024 pm 02:12 PM

GolangAPI中的缓存策略可提升性能和减轻服务器负载,常用策略有:LRU、LFU、FIFO和TTL。优化技巧包括:选择合适的缓存存储、分级缓存、失效管理以及进行监控和调整。实操案例中,使用LRU缓存优化从数据库获取用户信息的API,可从缓存中快速检索数据,否则从数据库中获取后再更新缓存。

PHP开发中的缓存机制与应用实战 PHP开发中的缓存机制与应用实战 May 09, 2024 pm 01:30 PM

在PHP开发中,缓存机制通过将经常访问的数据临时存储在内存或磁盘中来提升性能,从而减少数据库访问次数。缓存类型主要包括内存、文件和数据库缓存。PHP中可以使用内置函数或第三方库实现缓存,如cache_get()和Memcache。常见的实战应用包括缓存数据库查询结果以优化查询性能,以及缓存页面输出以加快渲染速度。缓存机制有效改善网站响应速度,提升用户体验并降低服务器负载。

PHP数组分页中如何使用Redis缓存? PHP数组分页中如何使用Redis缓存? May 01, 2024 am 10:48 AM

使用Redis缓存可以大幅优化PHP数组分页的性能。可通过以下步骤实现:安装Redis客户端。连接到Redis服务器。创建缓存数据,将每页数据存储到Redis哈希中,密钥为"page:{page_number}"。从缓存中获取数据,避免对大型数组进行昂贵的操作。

Win11英文21996怎么升级到简体中文22000_Win11英文21996升级到简体中文22000的方法 Win11英文21996怎么升级到简体中文22000_Win11英文21996升级到简体中文22000的方法 May 08, 2024 pm 05:10 PM

首先你需要将系统语言设置为简体中文显示并重启。当然,之前已经改为简体中文显示语言的直接跳过这一步即可。下面开始操作注册表,regedit.exe,左侧导航栏或上方地址栏直接定位到HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlNlsLanguage,然后将其中的InstallLanguage键值、Default键值全部修改为0804(如果想改为英文的en-us,需要先将系统显示语言设置为en-us,重启系统再全部修改为0409)进行到这里必须重启系

Win11下载的更新文件怎么找_Win11下载的更新文件位置分享 Win11下载的更新文件怎么找_Win11下载的更新文件位置分享 May 08, 2024 am 10:34 AM

1、首先双击打开桌面上的【此电脑】图标。2、接着双击鼠标左键进入【c盘】,系统文件一般都会自动存放在c盘。3、然后再c盘中找到【windows】文件夹,同样双击进入。4、进入【windows】文件夹后,找到其中的【SoftwareDistribution】文件夹。5、进入之后再找到【download】文件夹,里面存放的就是所有的win11下载更新文件了。6、如果我们想要删除这些文件的话,直接在这个文件夹中将他们删除就可以了。

PHP Redis 缓存应用与最佳实践 PHP Redis 缓存应用与最佳实践 May 04, 2024 am 08:33 AM

Redis是一个高性能键值对缓存。PHPRedis扩展提供了一个API来与Redis服务器交互。使用以下步骤与Redis连接,存储和检索数据:连接:使用Redis类连接到服务器。存储:使用set方法设置键值对。检索:使用get方法获取键的值。

PHP 数组和链表的算法时间复杂度比较 PHP 数组和链表的算法时间复杂度比较 May 07, 2024 pm 01:54 PM

数组和链表的算法时间复杂度比较:访问数组O(1),链表O(n);插入数组O(1),链表O(1)/O(n);删除数组O(1),链表O(n);搜索数组O(n),链表O(n)。

See all articles