范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

京东面试官讲讲Redis各个数据类型的底层数据结构

  前段时间有朋友面试京东的时候,遇到这样的面试题。  讲讲Redis的数据类型以及其对应的底层数据结构
  那么今天指北君带大家了解一下Redis基本数据类型对应的底层数据结构。  1. 前言
  Redis的键值对中的常见数据类型有String (字符串)、List(列表)、Hash(哈希)、Set(集合)、Zset(有序集合)。那么其对应的底层数据结构有SDS(simple dynamic string)、链表、字典、跳跃表、压缩列表、快速列表,整数集合等。
  下面我们来了解一下其底层数据结构的精妙之处。  2. Redis底层数据结构2.1 SDS
  Redis自定义了一种简单动态字符串(simple dynamic string),将其作为Redis的默认字符串表示。
  其主要结构如下:
  len表示这个SDS字符串长度,如果buff字节数组中保存了5个字符那么长度就是5。同时也方便获取SDS的长度。  alloc表示分配的字符数组长度。其值一般是大于SDS字符串长度(len),由于 Redis的设计场景就是会频繁的访问,修改数据,所以无论是数据增大或者是缩小都需要尽量减少重新分配内存的操作 。所以SDS会预留一些空间,在下一次修改数据的时候可以直接使用原先预分配的内存,同时在每次数据操作的时候也会动态的增加或者减少预留内存空间,。Redis3.0的版本的SDS结构中使用free, 表示未分配的空间,但也是同一个设计思想。  flags 标志位,低3位表示类型,其余5位未使用。  buf 实际存储数据的数组,可以保存字符,也可以保存二进制数据。
  redis6.0中SDS定义如下 (越来越节约使用内存了,内存是省出来的!)  typedef char *sds;  /* Note: sdshdr5 is never used, we just access the flags byte directly.  * However is here to document the layout of type 5 SDS strings. */ struct __attribute__ ((__packed__)) sdshdr5 {     unsigned char flags; /* 3 lsb of type, and 5 msb of string length */     char buf[]; }; struct __attribute__ ((__packed__)) sdshdr8 {     uint8_t len; /* used */     uint8_t alloc; /* excluding the header and null terminator */     unsigned char flags; /* 3 lsb of type, 5 unused bits */     char buf[]; }; struct __attribute__ ((__packed__)) sdshdr16 {     uint16_t len; /* used */     uint16_t alloc; /* excluding the header and null terminator */     unsigned char flags; /* 3 lsb of type, 5 unused bits */     char buf[]; }; struct __attribute__ ((__packed__)) sdshdr32 {     uint32_t len; /* used */     uint32_t alloc; /* excluding the header and null terminator */     unsigned char flags; /* 3 lsb of type, 5 unused bits */     char buf[]; }; struct __attribute__ ((__packed__)) sdshdr64 {     uint64_t len; /* used */     uint64_t alloc; /* excluding the header and null terminator */     unsigned char flags; /* 3 lsb of type, 5 unused bits */     char buf[]; };
  相对于C语言的字符串,SDS具有以下优点。  获取字符串的长度复杂度更低(常数复杂度)(复杂度可见此文章 看完这篇,还不清楚时间复杂度的,请来怼我)  更加节省内存(针对不同长度设置了不同的数据类型 sdshdr5、sdshdr8、sdshdr16等。)  杜绝缓冲区溢出  大大减少了修改字符串长度时所需要的内存分配次数  二进制安全  2.2 链表
  链表是大家比较熟悉的数据结构了,链表提供了高效的节点重排能力,顺序访问,通过增删节点调整长度等特点。Redis List对象的底层实现之一就是链表。
  每一个链表节点使用如下的结构来表示。  /* Node, List, and Iterator are the only data structures used currently. */  typedef struct listNode {     struct listNode *prev;     struct listNode *next;     void *value; } listNode;
  而多了listNode可以通过prev 和 next 指针组成一个双端队列如下图:
  多个节点可以组成一个链表,Redis使用了List结构来持有这些节点,操作更方便。其结构如下:  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链表具有如下特性:  由于是双端链表,有prev和next指针,获取节点的前置节点和后置节点的复杂度为O(1)  头节点的prev和尾节点的next均指向NULL,为无环链表,可以以NULL为链表访问终点  链表本身提供了指针,可以快速获取链表的表头节点和表尾节点  自带链表长度计数器,可以快速获取链表长度  链表可以保存各种不同类型的值  2.3 字典
  字典是一种用于保存键值对的抽象数据结构。
  在字典中,一个键(key)可以和一个值(value)进行关联,称之为键值对。字典中每个键都是独一无二的,并且程序都是通过key来操作更新value或者删除数据等。
  Redis的字典使用哈希表作为底层实现的, 一个哈希表可以包含多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。
  下面再讲一下哈希表,哈希表节点,以及字典的实现。
  哈希表及哈希表节点结构,字典结构 如下:  //字典结构 typedef struct dict {     dictType *type; // 类型特定的函数     void *privdata;  //私有数据     dictht ht[2]; //长度为2的哈希表数组, 一般情况下字典仅使用 ht[0]哈希表, ht[1]在rehash的时候会使用到。     long rehashidx; /* 未进行rehash的时候 rehashidx == -1 */     unsigned long iterators; /* number of iterators currently running */ } dict;  //哈希表结构 typedef struct dictht {     dictEntry **table; //哈希数组     unsigned long size; //哈希表大小     unsigned long sizemask; //哈希表掩码,用于计算索引值, 总是等于size-1     unsigned long used; //表示已有节点数量 } dictht;  //哈希节点 typedef struct dictEntry {     void *key; //键     union { //value值,包含了多种类型的值,不同类型的值可以使用不同的数据结构存储,节省内存。         void *val;  //其值可以是一个指针         uint64_t u64; //其值也可以是一个uint64_t 整数         int64_t s64; //其值可以是一个int64_t 整数         double d; //其值可以是一个double      } v;     struct dictEntry *next; //指向像一个哈希节点 } dictEntry;
  普通状态下的字典结构示意图:
  添加新建的机制是大家比较熟悉的思路啦。  使用字典设置的哈希函数计算key的哈希值,  哈希值与sizemask 进行 & 运算,计算出索引值。然后加入到数组中。
  如果出现哈希冲突,那么会使用链地址法解决冲突,使用next指针指向下一个哈希节点。
  哈希表保存的键值逐渐增多或者减少地过程中,程序会对哈希表进行扩展或者收缩,这个过程称之为rehash。
  rehash过程中会使用上面的ht[0] ht[1],具体过程这里就不详细说了,会另外专门写一篇来介绍。  2.4 跳跃表
  跳跃表(skiplist )是一种有序数据结构,它在每个节点中维护多个指向其他节点的指针,从而可以快速访问。其支持平均O(logN) 复杂度的节点查找。
  Zset使用了跳跃表和字典作为其底层实现。其好处就是可以进行高效的范围查询,也可以进行高效的单点查询。
  在源码中其结构如下:  typedef struct zskiplistNode { //跳跃表节点     sds ele; //成员对象,各个节点中成员对象唯一的。     double score; //分值     struct zskiplistNode *backward; //后退指针     struct zskiplistLevel { //层, 最大32层         struct zskiplistNode *forward; //前进指针         unsigned long span; //跨度     } level[]; } zskiplistNode;  typedef struct zskiplist { //跳跃表     struct zskiplistNode *header, *tail;//指向 跳跃表头 和表尾节点     unsigned long length; // 节点数量     int level; //跳跃表中层数最大的节点层数量 } zskiplist;  typedef struct zset { // zset的数据结构使用了 字典dict 和 zskiplist     dict *dict;     zskiplist *zsl; } zset;
  关于跳跃表节点的各参数解释如下:  层 level:跳跃表节点的level数组可以包含多个节点元素,每个元素都包含指向其他节点的指针,程序可以通过这些指针来加快访问其他节点的速度,层数越多访问效率越高。每创建一个新的跳跃表节点,程序会随机生成一个1-32之间层数的值,作为level数组的大小。表示节点的高度。  前进指针,forward :每层有一个指向表尾方向的前进指针,可以通过表头向表尾访问节点。  跨度 span:记录两个节点之间的距离。  后退指针 backward:节点的后退指针,用于从表尾向表头访问节点,每个节点只有一个后退指针,所以每次只能后退一个节点。  分值 score:分值是一个浮点数,跳跃表中的节点按照分值来排序,  成员对象 ele:一个指针,指向一个SDS对象。
  下面我们画一个跳跃表的示意图:
  图中如果要访问节点3,则只需要通过头节点第四层的前进指针,就可以找到此节点。
  如果需要增加元素的时候,会先使用高层 前进指针 去遍历,并对比score值,然后逐步缩小插入元素的位置范围,然后确定最终的位置。这样其平均时间复杂度为O(logN). 相比于链表的O(N)的时间复杂度来说,其效率大大提高。只不过其代价就是需要多一点的内存空间。
  个人感觉和MySql的索引有点类似。  2.5压缩列表 ziplist
  压缩列表是 Redis中list和 hash 对象的底层实现之一。压缩列表是为了节约内存而开发的,是由一系列连续编码的内存块组成。其结构示意如下:
  其中各节点说明如下:  zlbytes ,4字节, 记录压缩列表占用的内存字节数,对压缩列表仅进行内存重分配,或计算zlend尾节点位置时使用。  zltail ,4字节, 记录压缩列表尾节点距离压缩列表的起始地址有多少个字节。可以快速计算得到尾节点的地址  zlen, 2字节 ,记录了压缩列表包含的节点数量  entry X 压缩列表中的节点,其长度取决于节点内容  zlend , 1字节 , 特殊值OxFF (255) 标记压缩列表末端
  其中每一个压缩列表节点entry由如下部分组成:
  previous_entry_length 记录了压缩列表中前一个节点的长度,其属性可以是1或5个字节。如果前一个节点的长度小于254,那么其长度就是1字节,表示前一节点的长度。如果迁移节点的长度大于254字节, 那么其属性长度则为5个字节,其中第一个字节会设置为OxFE(254) , 后面的4个字节用来表示前一节点的长度。  encoding 表示content 属性保存的数据类型以及长度。content保存字节数组时,encoding的值为1,2,5字节, 最高前两位分别为00,01,10, 最高前两位后面的其他位数则记录content的长度。content保存整数编码时,最高2位为11, 其余位记录整数类型及。  content 保存节点的内容,可以保存字节数组或者整数。
  由于previous_entry_length 记录了前一个节点的长度,而且其可能为1个字节或者5个字节,如果前一个节点的长度从254之下增加到254之上,那么previous_entry_length 的值就要使用5个字节类表示。而如果后面的节点均存在同样的情况,那么压缩列表就会出现 连锁更新 ,导致内存空间重新分配,从而导致压缩列表增加节点或者删除节点的最坏时间复杂度位O(N2).
  新版本的redis中,引入了 listpack 。其目的是替换ziplist,整体结构类似。
  其entry结构如下:
  len保存了当前节点encoding及data的长度综合,从而可以 避免连锁更新  2.6快速列表
  快速列表(quicklist)可以看成是双向链表和压缩列表的一种组合。Redis3.2之后 list对象使用快速列表作为其底层实现。
  快速列表使用了quickListNode节点组成双向链表,然后在每个快速列表节点内部使用压缩列表存储数据,从而可以控制压缩列表的长度,避免连锁更新带来的性能影响。
  其中快速列表的源码结构如下:  typedef struct quicklist {     quicklistNode *head;     quicklistNode *tail;     unsigned long count;        /* total count of all entries in all ziplists */     unsigned long len;          /* number of quicklistNodes */     int fill : QL_FILL_BITS;              /* fill factor for inpidual nodes */     unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */     unsigned int bookmark_count: QL_BM_BITS;     quicklistBookmark bookmarks[]; } quicklist;  typedef struct quicklistBookmark {     quicklistNode *node;     char *name; } quicklistBookmark;  typedef struct quicklistNode {     struct quicklistNode *prev;     struct quicklistNode *next;     unsigned char *zl;     unsigned int sz;             /* ziplist size in bytes */     unsigned int count : 16;     /* count of items in ziplist */     unsigned int encoding : 2;   /* RAW==1 or LZF==2 */     unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */     unsigned int recompress : 1; /* was this node previous compressed? */     unsigned int attempted_compress : 1; /* node can"t compress; too small */     unsigned int extra : 10; /* more bits to steal for future usage */ } quicklistNode;
  quickList 中维护了一个quicklistBookmark数组,并且有执行qulickListNode 头尾的指针。
  每一个quicklistBookmark 中有一个quickListNode的指针,同时每一个quickListNode又有指向前一个后一个node的指针。
  其结构示意图如下:
  2.7 整数集合
  整数集合(intset) 是集合键底层实现之一,当一个集合只包含整数元素的时候,Redis会使用整数集合作为集合键的实现。
  整数集合的源码如下:  typedef struct intset {     uint32_t encoding;     uint32_t length;     int8_t contents[]; } intset;
  整数集合底层是一个数组,如果每一个元素都在16位以内的整数类型(-32768 到 32767),则数组的每个元素都为int16_t , 如果新加的整数超过这个范围,并且在32位以内的话, 整个数组中的每一个元素都会升级成int32_t 表示的整数, 如果新加入的是64 位才能表示的整数的话,所以的元素又会再一次升级。
  但是整数集合不支持降级,及 整数集合中如果有一个64位的整数,即使移除此元素,整个集合也不会降级。
  这样做具有一定的灵活性,而且可以节省内存。当需要升级的时候才进行升级。  总结
  通过以上 Redis 底层数据结构可以看出,其设计核心总是在节约内内存,提高访问速度。所以Redis快,这小巧妙的底层设计也是功不可没。同时我们也可以根据着些设计思想去优化我们自己的代码,优秀的设计总是值得去学习的。

USB接口WiFi模块,助力智慧安防领域实时高清视频数据传输智慧安防作为智慧城市中最为重要的物联网应用场景,一直广受关注,为实现安防工作的有效开展,更科学,更精准的掌控各项安防勤务部署,需要更多真实且实时性更高的数据信息,比如安防监控中的视2020慕尼黑上海电子展圆满落幕,SKYLABampampamp95POWER产品备受关注2020慕尼黑上海电子展于7月5日在上海国家会展中心落下帷幕,为期3天的展会,SKYLAB95POWER携旗下工业级车规级等高品质GNSS定位模块WiFi模块蓝牙模块蓝牙Beaco为什么智能控制首先选择BLE蓝牙模块SKYLAB蓝牙模块随着新基建热潮的兴起,人工智能物联网以及人工智能的纵向发展,基础连接数字化升级智能化控制的需求不断增长。越来越多的智能终端设备需要实现互联互通数据透传智能控制等需求。本篇SKYLA中国零售业复苏态势领跑全球阿里拆中台实施五新策略尼尔森中国零售业复苏态势领跑全球,线下渠道强势反弹线上增长回归理性线下渠道强势复苏尼尔森研究显示,进入第三季度,线上销售在增长同时开始回归理性,线下渠道则在疫情后强势反弹。受疫情影团餐行业信息化升级现状及数字化转型核心国家统计局数据显示,2019年我国餐饮行业市场规模已经超过4万亿元,达到4。67万亿元,占GDP总量达到4以上。其中,团餐占据了30市场份额,并且年均复合增长在20以上。预计到202021零售CIO俱乐部共话零售数字化餐饮数字化运营实践为企业实现数字化转型落地价值发出自己的光和热中国零售(餐饮)CIO俱乐部秘书长苏建东2020,不仅全国人民,全世界人民都度过了艰辛的一年,一方面在逆境中要保持身心健康,另一方面仍要SKYLAB盘点车载导航中常用的GNSS模块运用车载导航就是借助嵌入式GNSS模块,接收解调卫星的广播CA码信号。通过运算与每个卫星的伪距离,采用距离交会法得出接收机的经度纬度高度和时间修正量这四个参数。GPS模块通过串行通信口满足低功耗物联网数据传输刚需的串口WiFi模块介绍随着工业自动化和物联网的发展提速,开发人员开始寻求为网络边间处理应用实现连接提供数据透传的无线模块。为了顺应这种趋势,这种无线模块需要能够满足UART接口数据透明传输的低功耗模块。SKB501带您了解BLE5。0蓝牙模块的低功耗和高并发优势在万物联网时代,数据传输的症结在于带宽资源的紧缺,5G技术大范围普及后,带宽问题得到一定缓解,但是,在低功耗的物联网应用场景中,数据带宽要求本身就不高,它更注重功耗和高并发问题,54万导购服务2000万消费者,大商天狗的私域运营秘诀在企业数字化转型进入深水区后,越来越多的管理者意识到,连接消费者只是第一步,更重要的是运营和留存。疫情阴影之下,以商超百货为代表的实体零售业,遭遇了客流最不稳定的半年。据国家统计局2020百货购物中心数字化实践案例盘点突如其来的新冠肺炎疫情虽然重创了百货购物中心行业,但也逼迫企业迅速转变,锻炼了新的经营能力。放眼未来,行业整体向上的趋势并未改变,智慧化场景化注重体验将成为行业发展的新方向,开展线
互联网大厂校招名额大增的背后,程序员低门槛的时代一去不复返了文章首发于微信公众号非著名程序员,欢迎大家关注。大家好,我是校长。最近看到一条热搜话题,问互联网公司校招名额大幅增加说明了什么问题?这条新闻很长,我就不详细搬运了,大致意思就是阿里程序员没有真正的睡后收入,解决办法是利用复利思维放大复业收入文章首发于微信公众号非著名程序员,欢迎大家关注。大家好,我是校长。我很久之前不是做过一段时间的独立开发者吗?然后,自己曾经也分享过很多关于独立开发者,个人开发者相关的文章。很多程序美媒承认,美国例外逼迫中国逐渐强大,呼吁美国重审中国崛起全世界都是苦美久矣,究其原因就是美国利用霸权欺压全世界的所有国家,包括中俄这样的世界大国。美国之所以不受世界多国待见,其根本原因美国媒体今天给出了答案。美国国家利益杂志网站9月13简单说一说外卖这个行业简单说一说外卖这个行业前排提示本人文化有限,语言组织起来可能不是很流畅,老哥们就将就着看看吧!基本指针对美团饿了么,只说个大概,另外,我下面所说的所有关于外卖行业的东西都是基于我所重磅!最严限电令下,对亚马逊卖家有哪些影响?近期,广东江苏青海宁夏广西云南四川河南重庆内蒙古等地开始对能耗双控目标开展限电控能耗举措。当下正值跨境外贸旺季,此次限电令,让卖家上游产业受到直接影响。从国家发改委发布2021年上牵手比亚迪汉ev如果在10年前,让我花25万去买比亚迪,那绝对是不可能的事情。但是现在,比亚迪妥妥的是中国新能源一哥,三电和芯片全都自家生产,中国品牌的骄傲。从去年年底开始,就打算把那辆开了三年多三千档韵味向头戴耳机,MezeAntonio99Classics自从无线耳机兴起之后,好像已经很久没在三千档位看到突然出现很多热门讨论的头戴式耳机了,最近Meze99小不点GYFU以及和声力宙斯这三个讨论的比较多,后两只我都听过了,就差Meze收入财富文章首发于微信公众号涩郎,欢迎大家关注。大家好,我是涩郎,一名知识挖掘师兼知识布道师。收入财富!这个公式大家同意吗?其实,我感觉有时候厘清一个概念非常重要,正确认识一个概念之后,有美国撤销中国电信的市场授权,那么特斯拉是不是也应该让出位置来最近消息称美国又对中国电信下手了,到底是怎么回事呢?近几年,美国政府对于国内企业的打压是不间断。首先是华为和中兴的打压,现如今又把猪蹄子伸向中国电信,某国是不是数泰迪的哪哪都想搞一生鲜第一股上市首日破发,投资者被套牢,每日优鲜做错了什么在生鲜零售电商赛道上,每日优鲜和叮咚买菜厮杀激烈,就连上市也是争抢第一。不过,拿下生鲜零售电商第一股的每日优鲜,上市首日的表现却让投资者吃了大亏。6月25日,每日优鲜上市首日录得2众兴菌业欲饮酒,拟现金收购圣窖酒业,投资者切勿上头作为A股市场最好的赛道,白酒企业一直是个香饽饽。去年,郭广昌豪饮舍得酒业和金徽酒,赚得盆满钵满。如今,生产金针菇的众兴菌业也要来讨杯酒吃。6月20日,众兴菌业发布公告称,拟以现金方