Redis从入门到精通【高阶篇】之底层数据结构整数集(IntSet)详解

这篇具有很好参考价值的文章主要介绍了Redis从入门到精通【高阶篇】之底层数据结构整数集(IntSet)详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


Redis从入门到精通【高阶篇】之底层数据结构整数集(IntSet)详解

0.前言

上个篇章回顾,我们上个章节我们学习了《Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解》,我们从源码层了解字典是一种以键值对(key-value)形式存储数据的数据结构。在 Redis 中,字典使用哈希表来实现。哈希表是一种以常数时间复杂度 O(1) 进行插入、删除和查找的数据结构。了解到在 Redis 中,字典被广泛应用于实现哈希表和集合等数据结构。

本章节,我们详细了解一下在Redis又一个底层数据结构整数集(IntSet),它是用于存储整型数据,是一种紧凑的、高效的数据结构,可以用来实现集合等功能。

当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis 就会使用整数集合作为集合键的底层实现。

1.IntSet基本详解

整数集的实现方式与普通的数组和链表不同,它采用了一种特殊的压缩算法,可以在尽可能少的内存空间中存储大量的整型数据。在Redis中,整数集通常用来存储集合中的元素,例如有序集合中的分值。

整数集的结构示意如下所示:

+--------+--------+--------+--------+--------+--------+
| header |  data  |  data  |  data  |  data  |  data  |
+--------+--------+--------+--------+--------+--------+

整数集由一个头部和多个数据块组成。头部中存储了整数集的元素个数、编码方式和数据块的起始地址等信息。数据块中存储了实际的整型数据。

IntSet的设计目标是尽可能地节省内存空间,同时保证高效的操作性能。它可以存储三种类型的整数:8位整数、16位整数和32位整数。IntSet的内部结构示意如下图所示:

| 32位无符号整数 | 16位无符号整数 | 8位无符号整数 | 8位标志位 |

IntSet由一个数组和一个标志位组成。数组中按照从小到大的顺序存储了所有整数值,标志位用于记录数组中存储的整数类型(即8位整数、16位整数还是32位整数)。如果标志位为0,则表示数组中存储的都是8位整数;如果标志位为1,则表示数组中存储的都是16位整数;如果标志位为2,则表示数组中存储的都是32位整数。

在IntSet中,每个整数值只会出现一次,重复的整数会被自动去重。在插入新的整数值时,IntSet会自动根据当前数组中存储的整数类型进行扩容,以保证能够存储新的整数值。

IntSet支持的操作包括插入、删除、查找、遍历等。具体来说,插入和删除操作的时间复杂度均为O(1);查找操作的时间复杂度为O(N),其中N为IntSet中存储的整数数量;遍历操作的时间复杂度为O(N)。

在Redis6中,IntSet进行了一些优化。具体来说,Redis6中的IntSet支持了快速的非对称迭代操作,这意味着可以在不需要完全遍历整个IntSet的情况下,快速地获取满足特定条件的整数值。此外,Redis6中的IntSet还支持了压缩操作,可以通过压缩来减少IntSet占用的内存空间。
Redis从入门到精通【高阶篇】之底层数据结构整数集(IntSet)详解
在使用Redis时需要存储大量的整数值,可以考虑使用IntSet来优化存储空间和操作性能。在Redis6中,IntSet还支持了更多的优化和功能,可以更好地满足实际需求。
首先我们了解一下整数集的编码方式有三种:

  1. INTSET_ENC_INT16:表示整数集中的元素都是16位的整数。

  2. INTSET_ENC_INT32:表示整数集中的元素都是32位的整数。

  3. INTSET_ENC_INT64:表示整数集中的元素都是64位的整数。

整数集的编码方式是根据元素的大小来自动选择的。如果所有元素都可以放在16位或32位中,就选择相应的编码方式;否则,就选择64位编码方式。

整数集的压缩算法是在保证元素按照升序排列的前提下,尽量压缩每个元素的存储空间。具体来说,整数集会对连续的整型数据进行压缩,只存储它们的起始值和步长,而不是每个元素的实际值。这种算法可以在尽可能少的内存空间中存储大量的整型数据,提高了内存的利用率。
先不看源码,我们先看八股文。

1.1 整数集的压缩算法原理

整数集(IntSet)的压缩算法是一种特殊的算法,可以在尽可能少的内存空间中存储大量的整型数据。整数集的压缩算法基于以下两个原则:

  1. 整数集中的元素按照升序排列。

  2. 对于连续的整型数据,只存储它们的起始值和步长,而不是每个元素的实际值。

具体来说,整数集会根据元素的大小选择合适的编码方式(INTSET_ENC_INT16、INTSET_ENC_INT32或INTSET_ENC_INT64),然后将整数集中的元素按照升序排列。对于连续的整型数据,整数集会计算它们的起始值和步长,并将它们存储在数据块中。例如,假设整数集中有以下元素:1、2、3、4、5、10、11、12、13、14,整数集的数据块可以存储以下内容:

+--------+--------+--------+--------+--------+--------+--------+--------+
|   1    |   1    |   4    |   10   |   1    |   5    |   2    |   4    |
+--------+--------+--------+--------+--------+--------+--------+--------+

在上面的数据块中,第一个元素1表示整数集的编码方式(INTSET_ENC_INT16),第二个元素1表示整数集中有6个元素,后面的数据块中,每两个元素表示一个连续的整型数据的起始值和步长。例如,第三个元素4表示整数集中有4个连续的整数(4、5、6、7),第四个元素10表示这些整数的起始值,第五个元素1表示这些整数的步长。

整数集的压缩算法可以在尽可能少的内存空间中存储大量的整型数据,提高了内存的利用率。在实际的Redis应用中,整数集被广泛应用于集合等数据结构的实现。通过使用整数集,Redis可以在保证高效的操作性能的同时,减少内存的浪费,提高内存利用率。

1.2 整数集编码方式选择原理

虽然在Redis中,整数集(IntSet)的编码方式是根据元素的大小来自动选择的。整数集的编码方式有三种:INTSET_ENC_INT16、INTSET_ENC_INT32和INTSET_ENC_INT64,分别表示整数集中的元素都是16位、32位和64位的整数。

1.2.1 判断逻辑

当向整数集中添加元素时,Redis会根据新元素的大小和整数集中已有元素的大小,自动选择合适的编码方式。具体来说,如果新元素的大小可以放在整数集的当前编码方式中,就直接将新元素添加到整数集中;否则,Redis会根据新元素的大小和已有元素的大小,选择一个更大的编码方式,并将整数集中的所有元素转换为新的编码方式,然后再将新元素添加到整数集中。

1.2.2 举例说明

例如,假设整数集中已有32位整数,此时向整数集中添加一个16位整数。由于16位整数可以放在32位整数中,因此Redis会直接将新元素添加到整数集中,不需要进行编码方式的转换。但是,如果向上面的整数集中添加一个64位整数,由于64位整数无法放在32位整数中,Redis会选择64位编码方式,并将整数集中的所有元素转换为64位编码方式,然后再将新元素添加到整数集中。

整数集的自动编码方式选择可以在保证高效性能的同时,减少内存的浪费,提高内存利用率。在实际的Redis应用中,整数集被广泛应用于集合等数据结构的实现。通过使用整数集,Redis可以在保证高效的操作性能的同时,减少内存的浪费,提高内存利用率。

2. 源码解析

说了在多理论的概念和举例都略显单薄,很多同学可能在想"talk is cheap, show me code"。那么接下来我们进行一下源码解读。
在Redis的GitHub仓库中,IntSet的代码文件位于以下路径:
intset.h:https://github.com/redis/redis/blob/6.0/src/intset.h
intset.c:https://github.com/redis/redis/blob/6.0/src/intset.c
6.0分支是Redis6的分支,包含了最新的代码修改和功能更新。所以如果有同学想详细的了解一下,也可以在该分支下找到最新的IntSet代码文件,如果对Redis7的源码也想了解,只需要在上面切换一下分支即可。参考代码实现来深入了解IntSet的数据结构和操作还是很简单的。其实相比较前几个章节学习的底层数据结构,intset 不算复杂的。
Redis从入门到精通【高阶篇】之底层数据结构整数集(IntSet)详解
注:如果你对C也算是了解的话,可能下面的内容就不必再看了,点击我上面的源码地址,扫一遍这两个文件基本上就一目了然。
如果你是C小白,请继续
上面截图中

intset.h:IntSet的头文件,定义了IntSet的结构体类型和相关函数的声明。
intset.c:IntSet源文件,实现了IntSet的各种操作函数,包括创建、添加、删除、查找和升级等操作函数。

从上面的源码截图我们可以看出来IntSet的定义如下:

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

IntSet是一个结构体类型,其中包含三个成员变量:

  • encoding:表示IntSet所使用的编码格式,可以是8位整数、16位整数或32位整数。
  • length:表示IntSet中存储的整数数量。
  • contents:表示IntSet中存储的整数值的数组,是一个灵活数组成员(flexible array member)。

接下来,我们来看一下IntSet的基本操作函数。

2.1. intsetNew

intsetNew函数用于创建一个新的IntSet结构体,并返回该结构体的指针。函数定义如下:

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

在该函数中,使用zmalloc函数动态分配了一段大小为sizeof(intset)的内存空间,并将其初始化为0。然后将该内存空间强制转换为intset类型,并将encoding设置为INTSET_ENC_INT16,表示使用16位整数编码方式。最后返回该IntSet结构体的指针。

2.2. intsetAdd

intsetAdd函数用于向IntSet中添加一个整数值,函数定义如下:

intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
    uint8_t valenc = _intsetValueEncoding(value);
    if (valenc > is->encoding) {
        return intsetUpgradeAndAdd(is,value,success);
    }
    ......
}

intsetAdd中首先调用_intsetValueEncoding函数判断value的编码方式,并将其赋值给valenc。然后判断valenc是否大于当前IntSet的编码方式,如果是,则调用intsetUpgradeAndAdd函数进行升级操作。否则,继续执行后面的操作。

接下来,根据valenc的值,将value插入到IntSet中。具体来说,如果valenc等于8,则将value转换为8位整数,并插入到IntSet的数组中;如果valenc等于16,则将value转换为16位整数,并插入到IntSet的数组中;如果valenc等于32,则将value转换为32位整数,并插入到IntSet的数组中。插入操作完成后,将success设置为1,表示插入成功。

2.3. intsetRemove

intsetRemove函数用于从IntSet中删除一个整数值,函数定义如下:

int intsetRemove(intset *is, int64_t value, int *success) {
    uint8_t valenc = _intsetValueEncoding(value);
    uint8_t *p;
    uint64_t u64;
    int32_t i32;
    int16_t i16;
    uint8_t i8;
    if (valenc <= is->encoding) {
        ......
    }
    *success = 0;
    return 0;
}

在该函数中,首先调用_intsetValueEncoding函数判断value的编码方式,并将其赋值给valenc。然后判断valenc是否小于等于当前IntSet的编码方式,如果是,则继续执行后面的操作。否则,直接返回0,表示删除失败。

接下来,根据valenc的值,在IntSet的数组中查找value,并将其删除。具体来说,如果valenc等于8,则将value转换为8位整数,并在数组中查找并删除;如果valenc等于16,则将value转换为16位整数,并在数组中查找并删除;如果valenc等于32,则将value转换为32位整数,并在数组中查找并删除。删除操作完成后,将success设置为1,表示删除成功。

2.4. intsetFind

intsetFind函数用于在IntSet中查找一个整数值,函数定义如下:

uint8_t intsetFind(intset *is, int64_t value) {
    uint8_t valenc = _intsetValueEncoding(value);
    if (valenc <= is->encoding) {
        ......
    }
    return 0;
}

在该函数中,首先调用_intsetValueEncoding函数判断value的编码方式,并将其赋值给valenc。然后判断valenc是否小于等于当前IntSet的编码方式,如果是,则继续执行后面的操作。否则,直接返回0,表示查找失败。

接下来,根据valenc的值,在IntSet的数组中查找value。具体来说,如果valenc等于8,则将value转换为8位整数,并在数组中查找;如果valenc等于16,则将value转换为16位整数,并在数组中查找;如果valenc等于32,则将value转换为32位整数,并在数组中查找。如果在数组中找到了value,则返回1,表示查找成功;否则返回0,表示查找失败。

2.5. intsetUpgradeAndAdd

intsetUpgradeAndAdd函数用于将IntSet的编码方式升级,并向IntSet中添加一个整数值,函数定义如下:

/* Upgrades the intset to a larger encoding and inserts the given integer. */
static intset *intsetUpgradeAndAdd(intset *is, int64_t value, uint8_t *success) {
    uint8_t curenc = is->encoding;
    uint8_t newenc = _intsetValueEncoding(value);
    int length = is->length;
    int prepend = value < 0 ? 1 : 0;
    is->encoding = newenc;
    while(length--) {
        int64_t v;
        _intsetGet(is,length,&v);
        intsetRemove(is,v,NULL);
        intsetAdd(is,v,success);
    }
    *success = intsetAdd(is,value,success);
    return is;
}

在该函数中,首先获取当前IntSet的编码方式curenc、新的整数值value的编码方式newenc和IntSet的长度length。然后根据value的正负性,判断是否需要在数组的开头添加新的整数值。
接下来,将IntSet的编码方式设置为newenc,并遍历IntSet中的每个整数值。对于每个整数值,先在IntSet中删除,然后重新添加到IntSet中,这样可以将所有整数值的编码方式都升级到newenc。
最后,向IntSet中添加新的整数值value,并将success设置为1,表示添加成功。

2.6 收获

通过扫一遍源码我们基本上可以了解到IntSet提供了多种操作函数,包括添加整数、删除整数、查找整数、升级整数集合的操作等。了解到这些操作函数的实现原理,包括如何进行二分查找、如何调整整数集合的大小等。它可以在O(log N)的时间复杂度内完成查找、添加、删除操作等。如何利用连续内存和二分查找来提高性能等。

3.总结

在实际的Redis应用中,整数集被广泛应用于集合等数据结构的实现。通过使用整数集,Redis可以在保证高效的操作性能的同时,减少内存的浪费,提高内存利用率。可以用于存储大量的整数值,并支持快速的插入、删除和查找操作。如果您需要存储大量的整数值,可以考虑使用IntSet来优化存储空间和操作性能。

4.思考题

应一位网友的建议,在每个章节后面留个思考题,供大家继续学习。下次分享来揭晓答案。本次的思考题只有一道。
1. Redis6是如何使用IntSet来实现集合和有序集合?

5. Redis从入门到精通系列文章

《Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解》
《Redis从入门到精通【高阶篇】之底层数据结构快表QuickList详解》
《Redis从入门到精通【高阶篇】之底层数据结构简单动态字符串(SDS)详解》
《Redis从入门到精通【高阶篇】之底层数据结构压缩列表(ZipList)详解》
《Redis从入门到精通【进阶篇】之数据类型Stream详解和使用示例》
Redis从入门到精通【高阶篇】之底层数据结构整数集(IntSet)详解
大家好,我是冰点,今天的Redis从入门到精通【高阶篇】之底层数据结构整数集(IntSet)详解,全部内容就是这些。如果你有疑问或见解可以在评论区留言。文章来源地址https://www.toymoban.com/news/detail-489195.html

到了这里,关于Redis从入门到精通【高阶篇】之底层数据结构整数集(IntSet)详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis - 底层数据结构

    Redis 的底层数据结构主要以下几种: SDS(Simple Dynamic String, 简单动态字符串) ZipList(压缩列表) QuickList(快表) Dict(字典) IntSet(整数集合) ZSkipList(跳跃表) 在 Redis 中,并不会直接使用 C 语言自带的字符串结构作为实际的存储结构,而只是将字符串作为字面量使用,大多数情况使用自

    2023年04月12日
    浏览(44)
  • Redis五种数据结构底层编码结构

    Redis中的 任意数据类型的键和值都会被封装为一个RedisObject ,也叫做Redis对象,源码如下: 对象头不包含数据就已经占16字节,如果数据存string型,一个string一个对象头比较浪费空间,存大量数据时还是建议使用集合,这样可以共用一个对象头更加节省空间 Redis中会根据存储

    2024年02月11日
    浏览(41)
  • redis 底层数据结构详解

    目录   1.字符串 1.1 SDS定义 1.2 SDS1好处 2.列表 2.1 void 实现多态 3 字典 3.1   底层实现是hash表 3.2 字典结构 3.3 哈希算法 3.3.1 rehash 3.3.2 rehash的触发时机 3.3.3 渐进式rehash 扩展或收缩哈希表需要将ht[0]里面的所有键值对rehash到ht[1]里面,但是,这个rehash动作并不是一次性、集中式

    2023年04月09日
    浏览(48)
  • Redis - 数据类型映射底层结构

    从数据类型上体现就是,同一个数据类型,在不同的情况下会使用不同的编码类型,底层所使用的的数据结构也不相同。 字符串对象的编码可以是 int 、 raw 和 embstr 三者之一。 embstr 编码是专门用于保存简短字符串的一种优化编码方式,与 raw 编码会调用两次内存分配函数分

    2023年04月21日
    浏览(38)
  • redis的hash数据结构底层简记

    hash:k和v都是string的hash表。 HSET(设置集合数据,4.0之前只能设置1个,之后可以设置多个),HSETNX(若k不存在则设置对应v),HDEL(删除指定kv,可以一次删除多个),DEL(删除Hash对象),HMSET(设置多个kv,4.0之后废弃),HGETALL(查找全部数据),HGET(查询k对应的v),HLEN(查

    2024年02月21日
    浏览(37)
  • 【redis】redis的5种数据结构及其底层实现原理

    Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(无序集合)及zset(有序集合)。 在秒杀项目里,我用过redis的Set和Hash结构: String:一个 key 对应一个字符串,string是Redis 最基本的数据类型。(字节的abase框架只实现了redis的string数据结构,导致我们如

    2024年02月09日
    浏览(75)
  • 【从零开始学习Redis | 第八篇】认识Redis底层数据结构(下)

    目录 前言:   ZipList: Ziplist的特性: QucikList: QuicList特征: SkipList: 跳表特征: RedisObijct:  小心得: 总结:           在现代软件开发中,数据存储和处理是至关重要的一环。为了高效地管理数据,并实现快速的读写操作,各种数据库技术应运而生。其中,Redis作为一种

    2024年04月12日
    浏览(55)
  • Redis追本溯源(二)数据结构:String、List、Hash、Set、Zset底层数据结构原理

    Redis 并没有直接用 C 语言的字符串,而是自己搞了一个 sds 的结构体来表示字符串,这个 sds 的全称是 Simple Dynamic String,翻译过来就是“简单的动态字符串”。 安全的二进制存储 资源。关于sds的扩容和缩容下面会进行详细的介绍,这里先不赘述了。 在一些情况中,我们需要

    2024年02月16日
    浏览(55)
  • 【C++入门】STL容器--vector底层数据结构剖析

    目录  前言  1. vector的使用       vector的构造  vector迭代器  vector空间相关的接口  vector 功能型接口  find  swap  insert  erase 2. vector内部数据结构剖析 reserve  push_back和pop_back size、capacity、empty、operator[ ];  insert和erase resize swap  拷贝构造和赋值重载 构造函数补充  迭代器

    2024年01月25日
    浏览(57)
  • 数据结构从入门到精通——栈

    栈,作为一种后进先出(LIFO)的数据结构,在计算机科学中扮演着重要的角色。它的特性使得它在处理函数调用、括号匹配、表达式求值等问题时具有得天独厚的优势。然而,如果我们跳出传统思维的束缚,会发现栈的用途远不止于此。 在现代软件开发中,栈的概念被广泛

    2024年03月11日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包