redis中整数集合intset相关的文件为:intset.h与intset.c

intset的所有操作与操作一个排序整形数组 int a[N]类似,只是根据类型做了内存上的优化。

一、数据结构

 typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;

intset的数据结构比较简单,使用了一个变长结构体,成员length记录当前成员数量,成员encoding记录当前的int类型,共有以下三种:

 #define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

并使用以下方法进行判断类型:

 static uint8_t _intsetValueEncoding(int64_t v) {
if (v < INT32_MIN || v > INT32_MAX)
return INTSET_ENC_INT64;
else if (v < INT16_MIN || v > INT16_MAX)
return INTSET_ENC_INT32;
else
return INTSET_ENC_INT16;
}

intset是已排序好的整数集合,其大致结构如下:

 /*
+--------+--------+--------...--------------+
|encoding|length |contents(encoding*length)|
+--------+--------+--------...--------------+
*/

intset严格按照小端字节序进行存储,不论机器的字节序类型。如果是大端机器,需要进行转换,才进行存储。endianconv.h中有如下定义:

 #if (BYTE_ORDER == LITTLE_ENDIAN)
#define memrev16ifbe(p) ((void)(0))
#define memrev32ifbe(p) ((void)(0))
#define memrev64ifbe(p) ((void)(0))
#define intrev16ifbe(v) (v)
#define intrev32ifbe(v) (v)
#define intrev64ifbe(v) (v)
#else
#define memrev16ifbe(p) memrev16(p)
#define memrev32ifbe(p) memrev32(p)
#define memrev64ifbe(p) memrev64(p)
#define intrev16ifbe(v) intrev16(v)
#define intrev32ifbe(v) intrev32(v)
#define intrev64ifbe(v) intrev64(v)
#endif

具体实现在endianconv.c中,此处略过。

二、创建

 intset *intsetNew(void) {
intset *is = zmalloc(sizeof(intset));
is->encoding = intrev32ifbe(INTSET_ENC_INT16);
is->length = ;
return is;
}

刚创建好的intset是空的,默认使用最小的类型。其结构为:

 /*此处用一根“-”表示一字节,后同
+----+----+
| 16| 0|
+----+----+
*/

三、 操作

若有以下intset:

 /*
+----+----+--+--+--+--+--+--+--+
| 16| 7| 1| 2| 3| 4| 5| 7| 8|
+----+----+--+--+--+--+--+--+--+
|contents */

现在插入一个数字6,需要调用以下方法:

 /* Insert an integer in the intset */
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
uint8_t valenc = _intsetValueEncoding(value);
uint32_t pos;
if (success) *success = ; /* Upgrade encoding if necessary. If we need to upgrade, we know that
* this value should be either appended (if > 0) or prepended (if < 0),
* because it lies outside the range of existing values. */
if (valenc > intrev32ifbe(is->encoding)) {
/* This always succeeds, so we don't need to curry *success. */
return intsetUpgradeAndAdd(is,value);
} else {
/* Abort if the value is already present in the set.
* This call will populate "pos" with the right position to insert
* the value when it cannot be found. */
if (intsetSearch(is,value,&pos)) {
if (success) *success = ;
return is;
} is = intsetResize(is,intrev32ifbe(is->length)+);
if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+);
} _intsetSet(is,pos,value);
is->length = intrev32ifbe(intrev32ifbe(is->length)+);
return is;
}

因int16_t足以存储数字“6”,所以新插入数字的int类型与intset一致,然后需要查找插入的pos:

 static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
int min = , max = intrev32ifbe(is->length)-, mid = -;
int64_t cur = -; /* The value can never be found when the set is empty */
if (intrev32ifbe(is->length) == ) {
if (pos) *pos = ;
return ;
} else {
/* Check for the case where we know we cannot find the value,
* but do know the insert position. */
if (value > _intsetGet(is,max)) {
if (pos) *pos = intrev32ifbe(is->length);
return ;
} else if (value < _intsetGet(is,)) {
if (pos) *pos = ;
return ;
}
} while(max >= min) {
mid = ((unsigned int)min + (unsigned int)max) >> ;
cur = _intsetGet(is,mid);
if (value > cur) {
min = mid+;
} else if (value < cur) {
max = mid-;
} else {
break;
}
} if (value == cur) {
if (pos) *pos = mid;
return ;
} else {
if (pos) *pos = min;
return ;
}
}

因intset是已排序好的,所以使用了二分查找。过程如下

 /*
find 6
+----+----+--+--+--+--+--+--+--+
| 16| 7| 1| 2| 3| 4| 5| 7| 8|
+----+----+--+--+--+--+--+--+--+
pos | 0| 1| 2| 3| 4| 5| 6|
step1 |min=0
|max=6
|mid=(0+6)>>1=3
|mid_val=4 pos | 0| 1| 2| 3| 4| 5| 6|
step2 |min=4
|max=6
|mid=(4+6)>>1=5
|mid_val=7 pos | 0| 1| 2| 3| 4| 5| 6|
step3 |min=4
|max=4
|mid=(4+4)>>1=5
|mid_val=5 pos | 0| 1| 2| 3| 4| 5| 6|
step4 |min=5
|max=4
min>max break
*/

6在intset中不存在,查找到需要插入到pos=5的位置,此时首先要扩展intset的content:

 static intset *intsetResize(intset *is, uint32_t len) {
uint32_t size = len*intrev32ifbe(is->encoding);
is = zrealloc(is,sizeof(intset)+size);
return is;
}

扩展后:

 /*
+----+----+--+--+--+--+--+--+--+--+
| 16| 7| 1| 2| 3| 4| 5| 7| 8| |
+----+----+--+--+--+--+--+--+--+--+
pos | 0| 1| 2| 3| 4| 5| 6| 7|
*/

然后把原来在pos=5及之后的所有的元素向后移一格:

 static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) {
void *src, *dst;
uint32_t bytes = intrev32ifbe(is->length)-from;
uint32_t encoding = intrev32ifbe(is->encoding); if (encoding == INTSET_ENC_INT64) {
src = (int64_t*)is->contents+from;
dst = (int64_t*)is->contents+to;
bytes *= sizeof(int64_t);
} else if (encoding == INTSET_ENC_INT32) {
src = (int32_t*)is->contents+from;
dst = (int32_t*)is->contents+to;
bytes *= sizeof(int32_t);
} else {
src = (int16_t*)is->contents+from;
dst = (int16_t*)is->contents+to;
bytes *= sizeof(int16_t);
}
memmove(dst,src,bytes);
}

移动后:

 /*
+----+----+--+--+--+--+--+--+--+--+
| 16| 7| 1| 2| 3| 4| 5| 7| 7| 8|
+----+----+--+--+--+--+--+--+--+--+
pos | 0| 1| 2| 3| 4| 5| 6| 7|
*/

其使用memmove,并不全修改未覆盖到的内存,所以此时pos=5的值 还是7

最后修改pos=5的值:

 static void _intsetSet(intset *is, int pos, int64_t value) {
uint32_t encoding = intrev32ifbe(is->encoding); if (encoding == INTSET_ENC_INT64) {
((int64_t*)is->contents)[pos] = value;
memrev64ifbe(((int64_t*)is->contents)+pos);
} else if (encoding == INTSET_ENC_INT32) {
((int32_t*)is->contents)[pos] = value;
memrev32ifbe(((int32_t*)is->contents)+pos);
} else {
((int16_t*)is->contents)[pos] = value;
memrev16ifbe(((int16_t*)is->contents)+pos);
}
}

修改后并增加了length:

 /*
+----+----+--+--+--+--+--+--+--+--+
| 16| 8| 1| 2| 3| 4| 5| 6| 7| 8|
+----+----+--+--+--+--+--+--+--+--+
pos | 0| 1| 2| 3| 4| 5| 6| 7|
*/

如果此时要插入的数字是65536,超出了int16_t所能表示的范围,要先进行扩展int类型操作:

 static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
uint8_t curenc = intrev32ifbe(is->encoding);
uint8_t newenc = _intsetValueEncoding(value);
int length = intrev32ifbe(is->length);
int prepend = value < ? : ; /* First set new encoding and resize */
is->encoding = intrev32ifbe(newenc);
is = intsetResize(is,intrev32ifbe(is->length)+); /* Upgrade back-to-front so we don't overwrite values.
* Note that the "prepend" variable is used to make sure we have an empty
* space at either the beginning or the end of the intset. */
while(length--)
_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc)); /* Set the value at the beginning or the end. */
if (prepend)
_intsetSet(is,,value);
else
_intsetSet(is,intrev32ifbe(is->length),value);
is->length = intrev32ifbe(intrev32ifbe(is->length)+);
return is;
}

因其超出原来的int类型所能表示的范围,若为正数,一定是最大的,则应该插入在intset最后,否则应该在最前面。扩展完之后,从后往前将原来的数字,以新的int类型,放置在新的位置上,保证不会有未处理的数字被覆盖,处理完整。

删除操作:

 intset *intsetRemove(intset *is, int64_t value, int *success) {
uint8_t valenc = _intsetValueEncoding(value);
uint32_t pos;
if (success) *success = ; if (valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,&pos)) {
uint32_t len = intrev32ifbe(is->length); /* We know we can delete */
if (success) *success = ; /* Overwrite value with tail and update length */
if (pos < (len-)) intsetMoveTail(is,pos+,pos);
is = intsetResize(is,len-);
is->length = intrev32ifbe(len-);
}
return is;
}

找到指定元素之后,直接把后面的内存移至前面,然后resize。

redis 5.0.7 下载链接

http://download.redis.io/releases/redis-5.0.7.tar.gz

源码阅读顺序参考:

https://github.com/huangz1990/blog/blob/master/diary/2014/how-to-read-redis-source-code.rst

redis 5.0.7 源码阅读——整数集合intset的更多相关文章

  1. redis 5.0.7 源码阅读——跳跃表skiplist

    redis中并没有专门给跳跃表两个文件.在5.0.7的版本中,结构体的声明与定义.接口的声明在server.h中,接口的定义在t_zset.c中,所有开头为zsl的函数. 一.数据结构 单个节点: t ...

  2. redis 5.0.7 源码阅读——字典dict

    redis中字典相关的文件为:dict.h与dict.c 与其说是一个字典,道不如说是一个哈希表. 一.数据结构 dictEntry typedef struct dictEntry { void * ...

  3. redis 5.0.7 源码阅读——双向链表

    redis中双向链表相关的文件为:adlist.h与adlist.c 一.数据结构 redis里定义的双向链表,与普通双向链表大致相同 单个节点: typedef struct listNode { ...

  4. redis 5.0.7 源码阅读——动态字符串sds

    redis中动态字符串sds相关的文件为:sds.h与sds.c 一.数据结构 redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构 typedef c ...

  5. redis 5.0.7 源码阅读——压缩列表ziplist

    redis中压缩列表ziplist相关的文件为:ziplist.h与ziplist.c 压缩列表是redis专门开发出来为了节约内存的内存编码数据结构.源码中关于压缩列表介绍的注释也写得比较详细. 一 ...

  6. Linux 0.11源码阅读笔记-文件管理

    Linux 0.11源码阅读笔记-文件管理 文件系统 生磁盘 未安装文件系统的磁盘称之为生磁盘,生磁盘也可以作为文件读写,linux中一切皆文件. 磁盘分区 生磁盘可以被分区,分区中可以安装文件系统, ...

  7. Linux 0.11源码阅读笔记-中断过程

    Linux 0.11源码阅读笔记-中断过程 是什么中断 中断发生时,计算机会停止当前运行的程序,转而执行中断处理程序,然后再返回原被中断的程序继续运行.中断包括硬件中断和软件中断,硬中断是由外设自动产 ...

  8. Linux 0.11源码阅读笔记-总览

    Linux 0.11源码阅读笔记-总览 阅读源码的目的 加深对Linux操作系统的了解,了解Linux操作系统基本架构,熟悉进程管理.内存管理等主要模块知识. 通过阅读教复杂的代码,锻炼自己复杂项目代 ...

  9. redis 4.0.8 源码包安装集群

    系统:centos 6.9软件版本:redis-4.0.8,rubygems-2.7.7,gcc version 4.4.7 20120313,openssl-1.1.0h,zlib-1.2.11 y ...

随机推荐

  1. 实用代码|Linux定时检查应用状态

    有时候,我们挂在服务器上的应用会因为一些特殊情况挂掉,致使项目经理又对我们说:又挂了!赶紧去看看!于是又了以下脚本,使用shell编写,用于定时检查应用情况,挂掉则重启.这里以tomcat为例. 根据 ...

  2. python GUI测试自动化

    #! /usr/bin/env python#coding=GB18030'''GUI测试自动化 语言:python模块:pywinauto环境:windows7中文.python-2.6_32bit ...

  3. TCP/IP、TCP、UDP、Socket知识汇总

    带你了解TCP/IP,UDP,Socket之间关系 https://blog.csdn.net/chaoshenzhaoxichao/article/details/79785318 主要知识点: T ...

  4. GIMP(Linux下的Photoshop),Centos7下安装过程

    点当然是上官网:https://www.gimp.org/ 这英语看不懂,果断用谷歌的网页翻译. 点下载,就会有 看到这个,就点 下载一个安装的包 用命令行打上 [root@localhost 下载] ...

  5. Git详解之特殊配置与钩子应用

    前言 到目前为止,我阐述了 Git 基本的运作机制和使用方式,介绍了 Git 提供的许多工具来帮助你简单且有效地使用它. 在本章,我将会介绍 Git 的一些重要的配置方法和钩子机制以满足自定义的要求. ...

  6. Git详解之服务部署

    前言 到目前为止,你应该已经学会了使用 Git 来完成日常工作.然而,如果想与他人合作,还需要一个远程的 Git 仓库.尽管技术上可以从个人的仓库里推送和拉取修改内容,但我们不鼓励这样做,因为一不留心 ...

  7. Spring boot 学习中代码遇到的几个问题

    1.报一大段红错 此时对象还是能创建成功的,解决方案参考链接https://blog.csdn.net/wanglin199709/article/details/99121487 2.无法创建对象 ...

  8. Mysql Innodb cluster集群搭建

    之前搭建过一个Mysql Ndb cluster集群,但是mysql版本是5.7的,看到官网上mysql8的还是开发者版本,所以尝试搭建下mysql Innodb cluster集群. MySQL的高 ...

  9. kubernetes从私有仓库下载遇到的坑

    1.必须要在所有的k8s节点上配置私有仓库的地址.(master和node) 2.创建secret kubectl create secret docker-registry secret名字 --d ...

  10. python day01练习和作业

    习题:1.简述编译型与解释型语言的区别,且分别列出你知道的哪些语言属于编译型,哪些属于解释型编译型语言:优点:执行速度快 缺点:维护成本高,跨平台性差解释型语言:优点:维护成本低,跨平台性好 缺点:执 ...