redis 5.0.7 源码阅读——整数集合intset
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的更多相关文章
- redis 5.0.7 源码阅读——跳跃表skiplist
redis中并没有专门给跳跃表两个文件.在5.0.7的版本中,结构体的声明与定义.接口的声明在server.h中,接口的定义在t_zset.c中,所有开头为zsl的函数. 一.数据结构 单个节点: t ...
- redis 5.0.7 源码阅读——字典dict
redis中字典相关的文件为:dict.h与dict.c 与其说是一个字典,道不如说是一个哈希表. 一.数据结构 dictEntry typedef struct dictEntry { void * ...
- redis 5.0.7 源码阅读——双向链表
redis中双向链表相关的文件为:adlist.h与adlist.c 一.数据结构 redis里定义的双向链表,与普通双向链表大致相同 单个节点: typedef struct listNode { ...
- redis 5.0.7 源码阅读——动态字符串sds
redis中动态字符串sds相关的文件为:sds.h与sds.c 一.数据结构 redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构 typedef c ...
- redis 5.0.7 源码阅读——压缩列表ziplist
redis中压缩列表ziplist相关的文件为:ziplist.h与ziplist.c 压缩列表是redis专门开发出来为了节约内存的内存编码数据结构.源码中关于压缩列表介绍的注释也写得比较详细. 一 ...
- Linux 0.11源码阅读笔记-文件管理
Linux 0.11源码阅读笔记-文件管理 文件系统 生磁盘 未安装文件系统的磁盘称之为生磁盘,生磁盘也可以作为文件读写,linux中一切皆文件. 磁盘分区 生磁盘可以被分区,分区中可以安装文件系统, ...
- Linux 0.11源码阅读笔记-中断过程
Linux 0.11源码阅读笔记-中断过程 是什么中断 中断发生时,计算机会停止当前运行的程序,转而执行中断处理程序,然后再返回原被中断的程序继续运行.中断包括硬件中断和软件中断,硬中断是由外设自动产 ...
- Linux 0.11源码阅读笔记-总览
Linux 0.11源码阅读笔记-总览 阅读源码的目的 加深对Linux操作系统的了解,了解Linux操作系统基本架构,熟悉进程管理.内存管理等主要模块知识. 通过阅读教复杂的代码,锻炼自己复杂项目代 ...
- 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 ...
随机推荐
- 频繁插入(insert)的业务,用什么存储引擎更合适? | 数据库系列(转)
本文来自微信公众号 继续回答星球水友提问: 沈老师,MyISAM只支持表锁,但网上文章却说,在并发插入量比较大的时候,比较适合使用MyISAM,这矛盾吗? 这个问题,涉及MySQL表锁的一些细节,借着 ...
- influxdb基础那些事儿
InfluxDB是一个开源的时序数据库,使用GO语言开发,特别适合用于处理和分析资源监控数据这种时序相关数据.而InfluxDB自带的各种特殊函数如求标准差,随机取样数据,统计数据变化比等,使数据统计 ...
- python从excel中读取数据传给其他函数使用
首先安装xlrd库 pip install xlrd 方法1: 表格内容如下: 场景描述,读取该表格A列数据,然后打印出数据 代码何解析如下: import xlrd #引入xlrd库 def exc ...
- Python中将变量按行写入txt文本中
案例一: 讲数组a 循环写入名称为2.txt的文档中 # -*-coding:utf8-*- import requests from lxml import etree a=[1,2,3,4,5,6 ...
- CSS-04-层叠选择器
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- nginx之文件配置
nginx配置规则 nginx由受配置文件中指定的指令控制的模块组成 伪指令分为简单伪指令和块伪指令 简单的指令由名称和参数组成,这些名称和参数之间用空格分隔,并以分号(;)结尾 块指令的结构 与 简 ...
- laravel 工厂模式到容器
下面实现了查人拥有超能力的三种方式 第一种最基本的类引用实现 1 <?php /** * 目的:代码的完善来说明从 基础类的调用到 工厂类的使用 再到容器的出现的原因 * (首先要明白工厂类和容 ...
- Spring5.x源码分析 | 从踩坑到放弃之环境搭建
Spring5.x源码分析--从踩坑到放弃之环境搭建 前言 自从Spring发行4.x后,很久没去好好看过Spring源码了,加上最近半年工作都是偏管理和参与设计为主,在技术细节上或多或少有点疏忽,最 ...
- vs2017项目上传到github
如果要把项目提交到一个厂库里面,需要建个git存储库,比如选择新建git库选择VSVIEW文件夹,以后在这个文件夹下的项目,提交时都会提交到VSVIEW这个github仓库 选择的文件夹不在git文件 ...
- sql中常量和变量的引用
String name =jtf.getText().trim(); String sql="select * from stu where stuname=' "+name+& ...