redis底层数据结构之adlist

最近,我想通过redis的源码来学习redis。虽然平时工作中用得不多,不过对redis还是比较感兴趣的,毕竟它的性能是不错的。redis是一个开源的项目,我们可以通过源代码去了解redis。我后面会通过自己的学习,写一些关于redis源码的帖子。帖子的主要内容是分析代码设计,而并不会对源码进行详细解说。如果有不对的地方,请指正。源码是reids 3.0.3版本。

adlist

一、adlist,双链列表

数据结构定义如下:

//节点
typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;
//迭代器
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;

跟一般的双向链表差别不大。redis的双向链表是设计成一个比较通用的链表,所以value是指针,不限value的实际类型,另外给链表提供了用于复制、销毁、匹配的函数,使得链表的value可支持不同的内存管理方法,或者进行一些回调。

另外,还提供了迭代器,链表的迭代器通常都不是随机迭代器,只能一个元素一个元素地跳。由于这是双向链表,所以迭代器可以向前跳,也可以向后跳,不过redis只提供了向后跳的函数。迭代器也支持正向、反向,具体需要通过direction字段来指定方向。结合direction也可以实现迭代器向前跳,大概是这样adlist便没有提供向前跳的函数吧。

二、adlist的相关操作函数

主要有两类,一类是宏,一类是函数,看代码都比较简单,这里不对代码进行详解。只是简单列举一下函数。

/* Prototypes */
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);//查询
listNode *listIndex(list *list, long index);//获取指定位置的节点
void listRewind(list *list, listIter *li);//迭代器返回表头,正向迭代器
void listRewindTail(list *list, listIter *li);//迭代器到表尾,反向迭代器
void listRotate(list *list);//旋转链表,把表尾元素放到表头

三、adlist提供对象的内存管理

这里讲一下adlist如何通过dup,free进行内存管理。

有三个地方需要注意的:

a. 插入元素时,node中的value直接赋值为入参的value,不会调用dup函数。

b. listDup中,如果dup函数存在,调用dup函数去复制value。

c. listDelNode和listRelease中,如果free函数存在,通过free方法去销毁value。

通过上面三点,可以实现一些简单的内存管理方法,主要是对象内存的申请和销毁方面的管理。下面介绍一下简单的用法。

1) adlist不负责申请和销毁

如下面代码(代码没有通过编译测试,可能有误)

char s[] = “hello world”;
list *lst = listCreate();
//list的free,dup函数为空
listAddNodeTail(lst, &s[0]);
listAddNodeTail(lst, &s[1]);
listAddNodeTail(lst, &s[2]);
listAddNodeTail(lst, &s[3]);

//do some thing with lst
list *lst2 = listDup(lst);
//do some thing with lst2

listRelease(lst);
listRelease(lst2);

上面例子中,adlist不负责value内存空间的申请和销毁,完全由调用方管理。这时要求value所指定的元素的生存周期要比adlist的长,否则adlist中的value指定可能会失效。

2) adlist负责动态申请和销毁

如下面代码,(代码没有通过编译测试,可能有误):

void addStringToListHead(list *lst, char *s)
{
    char *snew = (char*)malloc(strlen(s)+1);
    assert(snew);
    strcpy(snew,s);
    listAddNodeHead(list,snew);
}
void *stringDup(void *s)
{
    char *sorg = (char*)s;
    char *snew = (char*)malloc(strlen(sorg)+1);
    assert(snew);
    strcpy(snew,sorg);
    return snew;    
}
void *stringFree(void *s)
{
    if (s) free(s);
}
int stringMatch(void *s1, void *s2)
{
    if (s1 && s2) return strcmp((char*)s1,(char*)s2) == 0 ? 1 : 0;
    if (!s1 && !s2) return 1;
    return 0;
}

list *lst = listCreate();
listSetDupMethod(lst, stringDup);
listSetFreeMethod(lst, stringFree);
listSetMatchMethod(lst, stringMatch);
addStringToListHead(lst,”hello”);
addStringToListHead(lst,”world”);

// do something
list *lst2 = listDup(lst);
// do something

listRelease(lst);
listRelease(lst2);

上面例子中动态生成的空间插入到adlist后,完全由adlist来负责管理,调用方不应该再长期持有或者删除动态空间的指针,否则会造成管理混乱。需要注意的是,adlist的dup函数需在复制时进行深度复制,否则一个动态申请的空间同时被两个指针长期保存,只要其中一个指针被free后,另一个指针就失效,再次访问或者free失效指针都将出错。

3) 未能友好地支持reference counting

未能友好支持reference counting的原因在于插入元素时,没有调用相关函数以增加引用计数。在复制list时通过dup来实现引用计数加1,在删除节点时通过free进行引用计数减1。但是插入元素时由于没有调用dup或是进行其他函数使引用计数加1,value便持有了指针,这使adlist不能友好地支持reference counting。不过这是可以通过其他方式实现支持的,如在插入前进行引用计数器加1,如果插入失败则引用计数减1。

四、容易产生内存碎片。

先不考虑value,只看node。可以看到,adlist进行插入元素或是复制链表时都需要申请节点空间,删除节点时需要释放空间。对于不断地进行插入和删除,如果所有的zmalloc、zfree函数没能作出很好的内存管理策略,很容易造成内容碎片。

五、总结

除了在内存管理上提供了调用方自定义的方法外,adlist跟一般的双向链表没有太大的区别,其特点就如上面所述,这里不再介绍了。

时间: 2024-10-21 14:41:48

redis底层数据结构之adlist的相关文章

redis 底层数据结构 压缩列表 ziplist

当一个哈希键只包含少量键值对,并且每个键值对的键和值要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做哈希键的底层实现. 压缩列表是Redis为了节约内存而开发的是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值 ziplist 数据结构 压缩列表节点的构成 每个压缩列表节点可以保存一个字节数组或者一个整数值,其中字节数组可以是以下三种长度的其中一种 长度小于等于63字节的字节数组 长度小

redis底层数据结构之dict 字典1

最近,我想通过redis的源码来学习redis.虽然平时工作中用得不多,不过对redis还是比较感兴趣的,毕竟它的性能是不错的.redis是一个 开源的项目,我们可以通过源代码去了解redis.我后面会通过自己的学习,写一些关于redis源码的帖子.帖子的主要内容是分析代码设计,而并不会对 源码进行详细解说.如果有不对的地方,请指正.源码是reids 3.0.3版本. dict 字典 一.数据结构 //字典条目 typedef struct dictEntry {     void *key;

redis底层数据结构之sds

最近,我想通过redis的源码来学习redis.虽然平时工作中用得不多,不过对redis还是比较感兴趣的,毕竟它的性能是不错的.redis是一个开源的项目,我们可以通过源代码去了解redis.我后面会通过自己的学习,写一些关于redis源码的帖子.帖子的主要内容是分析代码设计,而并不会对源码进行详细解说.如果有不对的地方,请指正.源码是reids 3.0.3版本. sds 一.redis的字符串 sds: c语言的字符串是 char *,redis则自己定义了内部的字符串sds,同时也提供了一组

redis底层数据结构之intset

最近,我想通过redis的源码来学习redis.虽然平时工作中用得不多,不过对redis还是比较感兴趣的,毕竟它的性能是不错的.redis是一个开源的项目,我们可以通过源代码去了解redis.我后面会通过自己的学习,写一些关于redis源码的帖子.帖子的主要内容是分析代码设计,而并不会对源码进行详细解说.如果有不对的地方,请指正.源码是reids 3.0.3版本. intset 一.intset数据结构 intset,是用于存储整数集合的数据结构.set的特点是无重复元素,元素可以无序.不过re

redis底层数据结构之dict 字典2

针对 上一文中提出的问题,这一次就进行解答: 由rehash过程可以看出,在rehash过程中,ht[0]和ht[1]同时具有条目,即字典中的所有条目分布在ht[0]和ht[1]中, 这时麻烦也就出来了.主要有以下问题:(现在暂不解答是如何解决的) 1.如何查找key. 2.如何插入新的key. 3.如何删除一个key. 4.如何确保rehash过程不断插入.删除条目,而rehash没有出错. 5.如何遍历dict所有条目,如何确保遍历顺序. 6.如何确保迭代器有效,且正确. 1. 如何查找ke

Redis底层数据结构----1 结构与命令

字符串的编码对象可以为 int raw embstr 如果字符串的长度大于32字节 那么字符串江永一个raw编码来实现sds  会调用两次分配内存函数来分别创建 redisobject和 sdshdr 小于32直接将用 embstr来编码 调用一次分配内存函数一起创建 redisobject和 sdshdr 列表的编码对象可以为ziplist 和 linkedlist hash对象的编码可以是 ziplist 和 hashtable 集合的编码实现是 intset  和 hashtable 有序

Redis 概念以及底层数据结构

Redis 简介REmote DIctionary Server(Redis) 是一个由SalvatoreSanfilippo写的key-value存储系统. Redis是一个开源的使用ANSI C语言编写.遵守BSD协议.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API. 它通常被称为数据结构服务器,因为值(value)可以是字符串(String), 哈希(Map), 列表(list), 集合(sets) 和有序集合(sorted sets)等类型. R

Redis各种数据结构性能数据对比和性能优化实践

很对不起大家,又是一篇乱序的文章,但是满满的干货,来源于实践,相信大家会有所收获.里面穿插一些感悟和生活故事,可以忽略不看.不过听大家普遍的反馈说这是其中最喜欢看的部分,好吧,就当学习之后轻松一下. Redis各种数据结构性能数据对比 测试工具:perf4j 性能指标:平均值,最小值,最大值,方差 对比将814条数据按单条插入到哈希MAP和哈希SET: 对比从814条数据的哈希MAP和哈希SET中判断一个元素是否存在(map的hasKey和set的isMember): 大量数据插入哈希MAP,运

Redis 基础数据结构与对象

Redis用到的底层数据结构有:简单动态字符串.双端链表.字典.压缩列表.整数集合.跳跃表等,Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包括字符串对象.列表对象.哈希对象.集合对象和有序结合对象共5种类型的对象. 1 简单动态字符串 redis自定义了简单动态字符串数据结构(sds),并将其作为默认字符串表示. struct sdshdr { unsigned int len; unsigned int free; char buf[