做一个网站需要多少钱大概费用,如何申请网站空间和域名,做网站需要考虑哪些,鲜花类网站建设策划书范文点击关注上方“Java大厂面试官”#xff0c;第一时间送达技术干货。阅读文本大概需要 8 分钟。前言今天来整理学习下Redis有哪些常用数据结构#xff0c;都是怎么使用的呢#xff1f;首先看下全局存储结构。全局存储结构基础你们肯定都知道#xff0c;redis支持的基础数据结… 点击关注上方“Java大厂面试官”第一时间送达技术干货。阅读文本大概需要 8 分钟。前言今天来整理学习下Redis有哪些常用数据结构都是怎么使用的呢首先看下全局存储结构。全局存储结构基础你们肯定都知道redis支持的基础数据结构如下: String(字符串)、List(链表)、Hash(哈希)、Set(集合)和 Sorted Set(有序集合)那我来给你整个的画一画redis全局存储结构模型。( redis版本不同代码也不尽相同但是看原理够用了)从redis源码开始分析首先是redis启动会初始化redisServer默认创建16个数据库redisDb默认我们用的都是第一个编号0的数据库。struct redisServer { // … // redis数据库数组 redisDb *db; // 数据库的数量 默认16 int dbnum; //...}每个redisDb数据库用dict(字典)保存着数据库中的所有键值对 struct redisDb { // 数据库键空间保存着数据库中的所有键值对 dict *dict; //...}dict(字典)使用一对hashtable哈希表实现跟Java中的HashMap很像typedef struct dict { // 包含2个hashtable dictht ht[2]; // ...} typedef struct dictht { // 哈希表数组 dictEntry **table; // ...} typedef struct dictEntry { // 键 void *key; // 值 void *val; // 指向下个哈希表节点形成链表 dictEntry *next;}字典中实际存储的Redis对象typedef struct redisObject { // 类型 unsigned type:4;// string,list,set,zset,hash等 // 编码 unsigned encoding:4; // int,raw,embstr,ziplist,intset,quicklist,skiplist等 // 对象最后一次被访问的时间 unsigned lru:REDIS_LRU_BITS; // 引用计数 int refcount; // 指向实际值的指针 void *ptr;} 从上面分析可得Redis全局存储结构如下在这里插入图片描述(这个图直接把我画裂开了,如有错误欢迎指正)下面我们用3w方法来一一介绍下每个数据类型底层所用到了哪些数据结构(编码 )。String 字符串是什么内部其实就是一个带长度信息的字节数组原理类似Java中的ArrayList可以动态扩容所以很多特性都类似了原理是相通的。内容是以二进制的形式存储的所以 SDS(Simple Dynamic String) 可以存储任何类型的二进制数据同时也不需要担心数据格式转换的问题。struct SDS { // ... T capacity; // 数组容量 T len; // 数组长度 byte[] content; // 数组内容} 在这里插入图片描述为什么1.为什么申请空间比实际占用空间大冗余了很多空位?字符串支持append修改操作如果没有冗余空间那么追加操作必会引起频繁的数组扩容而扩容是个耗时操作所以通过空间预分配的方式来解决即用冗余空间换时间。2.实际使用长度len字段存在的意义是什么我们来用反证法证明如果没有len来记录字符串长度那么每次获取字符串长度时就要调用默认的strlen函数来获取而这个函数的时间复杂度是O(n)如果有了len每次获取长度可以直接访问它时间复杂度立马降至为O(1)。查询效率迎来质的飞跃这块跟Arraylist的size原理一样。如何实现我们来直接用redis自带的debug命令看下实际存储对象的底层编码encoding来看下底层使用了什么数据结构。本文实例用的是redis版本6.0.6int编码 set key1 2000222222OK debug object key1Value at:0x7f21f2eadd20 refcount:1 encoding:int serializedlength:5 lru:13142802 lru_seconds_idle:25embstr编码 set key2 01234567890123456789012345678901234567890123 // 44个字符OK debug object key2Value at:0x7f21f2e15140 refcount:1 encoding:embstr serializedlength:21 lru:13145749 lru_seconds_idle:5raw编码 set key2 012345678901234567890123456789012345678901234 // 45个字符OK debug object key2Value at:0x7f21f2eadd40 refcount:1 encoding:raw serializedlength:21 lru:13145765 lru_seconds_idle:2总结为了节省内存空间会按照实际存储字符串长度类型来选用不同编码。存储的字符串可以转为long型则用long类型存储编码为int存储的字符串长度不大于44个字节时用embstr编码存储的字符串长度大于44个字节时用raw编码编码类型分这么细的原因为了优先使用更紧凑的数据结构来解决问题终极目标就是为了压缩内存、压缩内存、压缩内存。raw和embstr的区别embstr编码: RedisObject的元数据指针和SDS是连续的可以避免内存碎片raw编码: Redis会给SDS分配独立的空间并用指针指向SDS结构扩容策略字符串长度小于1MB时采用加倍策略ArrayList是1.5倍字符串长度大于1MB时采用每次扩容只加固定1MB这个扩容策略就比ArrayList高明了当字符串比较大时比如200M每次还是double的话400M那就太浪费空间了为了避免这种过大的空间浪费使用了这种阈值判断方式针对原始数据的不同大小采用相应的有效策略。Reids规定了字符串最大长度不能超过512MB。使用场景常用于缓存用户信息、原子加减。注意: 原子计数是有范围的(long的范围)超过了会报错异常List 链表是什么版本3.2之前在Redis中使用的是压缩列表ziplist双向链表linkedlist.版本3.2之后快速链表quickList3.2之前初始化的 List 使用的压缩列表ziplist随着数据增多转化为双向链表linkedlist。压缩列表转化成双向链表的条件如果添加的字符串元素长度超过默认值64zip包含的节点数超过默认值512这两个条件是可以修改的在redis.conf中list-max-ziplist-value 64 list-max-ziplist-entries 512 linkedlist原理类似Java中的LinkedList,增删时间复杂度O(1)查询O(n).typedef struct list{ //表头节点 listNode *head; //表尾节点 listNode *tail; //链表所包含的节点数量 unsigned long len; // ...}typedef struct listNode{ //前置节点 struct listNode *prev; //后置节点 struct listNode *next; //节点的值 void *value; }在这里插入图片描述ziplistziplist是什么ziplist压缩列表是内存地址连续元素之间紧凑存储功能类似链表的一种数据结构。struct ziplist { int32 zlbytes; // 整个列表占用字节数 int32 zltail_offset; // 达到尾部的偏移量 int16 zllength; // 存储元素实体个数 T[] entries; // 存储内容实体 int8 zlend; // 尾部标识}struct entry { int prevlen; // 前一个entry的字节长度int encoding;; // 元素类型编码 optional byte[] content; // 元素内容}在这里插入图片描述为什么用ziplist因为普通的链表要附加prev、next前后指针浪费空间(64位操作系统每个指针占用8个字节)另外每个节点的内存是单独分配会加剧内存的碎片化影响内存管理效率。如何实现简单的来说就是用非指针连接的方式实现了双向链表的能力能从头部和尾部(zltail)双向遍历没有维护双向指针prev next而是存储上一个 entry的长度和 当前entry的长度通过长度推算下一个元素在什么地方。牺牲读取的性能获得高效的存储空间因为(简短字符串的情况)存储指针比存储entry长度 更费内存。这是典型的“时间换空间”。只有字段、值比较小才会用ziplist。优点内存地址连续省去了每个元素的头尾节点指针占用的内存节省空间缺点插入数据、删除数据会导致连锁更新问题有点儿类似Arraylist为保证内存连续性的数据移动的原理quicklistquicklist是什么quickList是一个ziplist组成的双向链表。每个节点使用ziplist来保存数据。为什么为什么用quicklist结合了 zipList 和 linkedList 的优点设计出来的ziplist会引入频繁的内存申请和释放而linkedlist由于指针也会造成内存的浪费而且每个节点是单独存在的会造成很多内存碎片所以结合两个结构的特点设计了quickList。如何实现debug看下encoding quicklist rpush key3 a b c3 debug object key3Value at:0x7f21f2eaddb0 refcount:1 encoding:quicklist serializedlength:22 lru:13150287 lru_seconds_idle:17 ql_nodes:1 ql_avg_node:3.00 ql_ziplist_max:-2 ql_compressed:0 ql_uncompressed_size:20struct quicklist { quicklistNode *head; quicklistNode *tail; long count; // 元素总数 // ... }struct quicklistNode { quicklistNode *prev; quicklistNode *next; ziplist *zl;// 压缩列表quickList 的每个节点使用 ziplist 来保存数据有head 有tail每一个节点是一个quicklistNode包含prev和next指针。每一个quicklistNode 包含 一个ziplist*zp 压缩链表里存储键值。所以quicklist是对ziplist进行一次封装使用小块的ziplist来既保证了少使用内存也保证了性能。结构如下图在这里插入图片描述每个quicklist节点上的ziplist大小可以配置-5: 每个quicklist节点上的ziplist大小不能超过64 Kb。-4: 每个quicklist节点上的ziplist大小不能超过32 Kb。-3: 每个quicklist节点上的ziplist大小不能超过16 Kb。-2: 每个quicklist节点上的ziplist大小不能超过8 Kb。(默认值)-1: 每个quicklist节点上的ziplist大小不能超过4 Kb。list-max0ziplist-size -2中间节点压缩策略可配置0: 是个特殊值表示都不压缩。这是Redis的默认值。1: 表示quicklist两端各有1个节点不压缩中间的节点压缩。2: 表示quicklist两端各有2个节点不压缩中间的节点压缩。以此类推list-compress-depth 0总结整个redis全局存储模型是用字典完成的类似Java中的HashMap原理String类型底层是动态字符串会根据字符串类型和大小决定使用int编码、raw编码或者embstr编码。List类型3.2版本之前会根据数据大小判断用ziplist还是linkedlist3.2版本之后优化为quicklist方式编码。参考《Redis深度历险 核心原理与应用实践》https://juejin.cn/post/6863256540439117831往期推荐看故事学知识-三年工龄了还讲不清redis持久化生产环境下如何排查CPU异常定位鬼畜代码为什么JDK源码中无限循环大多使用for(;;)而不是while(true)?一起进大厂成为架构师长按加关注