Redis设计与实现读书笔记——双链表
前言
概述
数据结构
/*
* 链表节点
*/
typedef struct listNode { // 前驱节点
struct listNode *prev; // 后继节点
struct listNode *next; // 值
void *value; } listNode;
每个双端链表都被一个list结构包装起来,list结构带有两个指针,一个指向双端链表的表头节点,另一个指向双端链表的表尾节点,这个特性使得Redis可以很方便执行像RPOP LPUSH这样的命令:
/*
* 链表
*/
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;
链表结构中还有三个函数指针 dup, free 和match,这些指针指向那些用于处理不同类型值的函数
list结构和listNode结构的API
)
list *listCreate(void)
/**
* 创建一个新列表
*
* T = O(1)
*/
list *listCreate(void)
{
struct list *list; // 为列表结构分配内存
list = (struct list *)malloc(sizeof(struct list));
if (list == NULL)
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)
/**
* 释放整个列表
*
* T = O(N), N为列表长度
*/
void listRelease(list *list)
{
unsigned long len;
listNode *current, *next; current = list->head;
len = list->len; while (len --) {
next = current->next;
// 如果列表有自带的free方法,那么先对节点值调用它
if (list->free) list->free(current->value);
// 之后释放节点
free(current);
current = next;
}
free(list);
}
list *listAddNodeHead(list *list, void *value)
/**
* 新建一个包含给定value的节点,并将它加入到列表的表头
*
* T = O(1)
*/
list *listAddNodeHead(list *list, void *value)
{
listNode *node; node = (listNode *)malloc(sizeof(listNode));
if (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;
}
list *listAddNodeTail(list *list, void *value)
/**
* 新建一个包含给定value的节点,并把它加入到列表的表尾
*
* T = O(1)
*/
list *listAddNodeTail(list *list, void *value)
{
listNode *node; node = (listNode *)malloc(sizeof(listNode));
if (node == NULL)
return NULL; 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;
}
list *listInsertNode(list *list, listNode *old_node, void *value, int after)
/**
* 创建一个包含值value的节点
* 并根据after参数的指示,将新节点插入到old_node的之前或者之后
*
* T = O(1)
*/
list *listInsertNode(list *list, listNode *old_node, void *value, int after)
{
listNode *node; node = (listNode *)malloc(sizeof(listNode));
if (node == NULL)
return NULL; if (after) {
// 插入到old_node之后
node->prev = old_node;
node->next = old_node->next;
// 处理表尾节点
if (list->tail == old_node) {
list->tail = node;
}
} else {
// 插入到old_node之前
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)
/**
* 释放列表中给定的节点
*
* T = O(1)
*/
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); // 释放节点
free(node); // 更新列表节点数目
list->len --;
}
迭代器
/**
* 链表迭代器
*/
typedef struct listIter {
// 下一节点
listNode *next; // 迭代方向
int direction;
} listIter;
direction决定了迭代器是沿着next指针向后迭代,还是沿着prev指针向前迭代,这个值可以是adlist.h中的AL_START_HEAD常量或AL_START_TAIL常量:
#define AL_START_HEAD 0
#define AL_START_TAIL 1
学习一下迭代器的api实现:
listIter *listGetIterator(list *list, int direction)
/**
* 创建列表list的一个迭代器,迭代方向由参数direction决定
*
* 每次对迭代器listNext(),迭代器返回列表的下一个节点
*
* T = O(1)
*/
listIter *listGetIterator(list *list, int direction)
{
listIter *iter; iter = (listIter *)malloc(sizeof(listIter));
if (iter == NULL)
return NULL; // 根据迭代器的方向,将迭代器的指针指向表头或者表尾
if (direction == AL_START_HEAD) {
iter->next = list->head;
} else {
iter->next = list->tail;
} // 记录方向
iter->direction = direction; return iter;
}
void listRewind(list *list, listIter *li)
/**
* 将迭代器iter的迭代指针倒回list的表头
*
* T = O(1)
*/
void listRewind(list *list, listIter *li)
{
li->next = list->head;
li->direction = AL_START_HEAD;
}
void listRewindTail(list *list, listIter *li)
/**
* 将迭代器iter的迭代指针倒回list的表尾
*
* T = O(1)
*/
void listRewindTail(list *list, listIter *li)
{
li->next = list->tail;
li->direction = AL_START_TAIL;
}
listNode *listNext(listIter *iter)
/**
* 函数要么返回当前节点,要么返回NULL,因此,常见的用法是:
* iter = listGetIterator(list, <direction>);
* while ((node = listNext(iter)) != NULL) {
* doSomethingWith(listNodeValue(node));
* }
*
* T = O(1)
*/
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;
}
小结
Redis设计与实现读书笔记——双链表的更多相关文章
- Redis设计与实现读书笔记——简单动态字符串
前言 项目里用到了redis数据结构,不想只是简单的调用api,这里对我的读书笔记做一下记录.原文地址: http://www.redisbook.com/en/latest/internal-dat ...
- Redis设计与实现读书笔记(二) 链表
链表作为最基础的数据结构,在许多高级语言上已经有了很好的实现.由于redis采用C语言编写,需要自己实现链表,于是redis在adlist.h定义了链表类型.作者对于这部分没什么好说,源码比较简单,如 ...
- Redis设计与实现读书笔记(一) SDS
作为redis最基础的底层数据结构之一,SDS提供了许多C风格字符串所不具备的功能,为之后redis内存管理提供了许多方便.它们分别是: 二进制安全 减少字符串长度获取时间复杂度 杜绝字符串溢出 减少 ...
- Redis 设计与实现读书笔记一 Redis List
list结构体 adlist.h/list(源码位置) /* * 双端链表结构 */ typedef struct list { // 表头节点 listNode *head; // 表尾节点 lis ...
- Redis 设计与实现读书笔记一 Redis字符串
1 Redis 是C语言实现的 2 C字符串是 /0 结束的字符数组 3 Redis具体的动态字符串实现 /* * 保存字符串对象的结构 */ struct sdshdr { // buf 中已占用空 ...
- <<redis设计和实现>>读书笔记
redis如何实现主从同步的高效率?? 主从复制的同步有一个命令数据的同步文本,然后利用两个不同服务器的偏移量来进行进行同步,避免每次都是全部同步(并非会保存所有的命令数据,而是会有一个缓冲区(比如1 ...
- Linux内核设计与实现 读书笔记 转
Linux内核设计与实现 读书笔记: http://www.cnblogs.com/wang_yb/tag/linux-kernel/ <深入理解LINUX内存管理> http://bl ...
- 【2018.08.13 C与C++基础】C++语言的设计与演化读书笔记
先占坑 老实说看这本书的时候,有很多地方都很迷糊,但却说不清楚问题到底在哪里,只能和Effective C++联系起来,更深层次的东西就想不到了. 链接: https://blog.csdn.net/ ...
- Linux内核设计与实现 读书笔记
第三章 进程管理 1. fork系统调用从内核返回两次: 一次返回到子进程,一次返回到父进程 2. task_struct结构是用slab分配器分配的,2.6以前的是放在内核栈的栈底的:所有进程的ta ...
随机推荐
- cocos2d-x 精灵移动
在HelloWorldScene.h中声明 class HelloWorld : public cocos2d::CCLayer { public : ...... CCPoin ...
- cocos2dx+lua编译Android项目
一.简单介绍 cocos2dx版本号:3.2 二.问题及解决方式 1.为项目开启Native支持,把项目转为C++项目. 1>.项目开启C++ Native支持,操作例如以下图 watermar ...
- 了解sota字符界面(章节4.1)
4 SOTA操作 4.1 SOTA字符界面 sotaCC是字符界面管理sota系统程序 . 在/.../sota/bin/目录下,启动sotaCC.在终端的该目录下输入指令“./sotaCC”,启动s ...
- Skype无法显示登录界面
Skype升级之后突然抽风,双击运行程序之后,输入用户名和密码的窗口都没了,截图如下(本机为Windows 7 32bit版本): 卸载重新安装,也无济于事.删除注册表中的Skype的相关信息后问题依 ...
- Android网络:开发浏览器(五)——功能完善之保存图片实现
最近几天开学,所以没有更新博客.今天开始更新.最近我打算每天抽出一些事件看点Thinkin Java这本书,仔细研究下java,当然也会出这个博客关于Think in java系列的博客,大家可以一起 ...
- linux下编译原理分析
linux下编译hello.c 程序,使用gcc hello.c,然后./a.out就能够执行:在这个简单的命令后面隐藏了很多复杂的过程,这个过程包含了以下的步骤: ================= ...
- 菜鸟从零学编程(七)——搭建一个完整的Java开发环境
作为一个Java程序员,配置一个java开发环境是必备的技能,今天给广大菜鸟初学者补上一课.环境的配置,大概就分三个1,JDK 2,Tomcat(或者其他的)3,eclipse(或者myeclipse ...
- 万方数据知识平台 TFHpple +Xpath解析
试了一下.基本上适合全部的检索结果. - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loadi ...
- centos6安装bt工具transmission
centos6 install transmission 1. 安装所需的组件: yum -y install gcc gcc-c++ m4 make automake libtool gettex ...
- UVA 10313(完全背包变形)
Problem B Pay the Price Input: standard input Output: standard output Time Limit: 2 seconds Memory L ...