Skip to content

Commit 5043b5d

Browse files
committed
📝 redis-datatype
1 parent 27f31bc commit 5043b5d

File tree

7 files changed

+15
-37
lines changed

7 files changed

+15
-37
lines changed

docs/.DS_Store

0 Bytes
Binary file not shown.

docs/_images/.DS_Store

0 Bytes
Binary file not shown.

docs/_images/redis/c-string.jpg

323 KB
Loading

docs/_images/redis/redis-sds.jpg

916 KB
Loading
700 KB
Loading

docs/data-management/.DS_Store

0 Bytes
Binary file not shown.

docs/data-management/Redis/Redis-Datatype.md

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@
2222

2323
![redis-kv](https://tva1.sinaimg.cn/large/008i3skNly1gqsf47nimoj324o0u0hdt.jpg)
2424

25-
因为这个哈希表保存了所有的键值对,所以,也把它称为全局哈希表。哈希表的最大好处很明显,就是让我们可以用 $O(1)$ 的时间复杂度来快速查找到键值对——我们只需要计算键的哈希值,就可以知道它所对应的哈希桶位置,然后就可以访问相应的 entry 元素。
25+
因为这个哈希表保存了所有的键值对,所以,也把它称为**全局哈希表**。哈希表的最大好处很明显,就是让我们可以用 $O(1)$ 的时间复杂度来快速查找到键值对——我们只需要计算键的哈希值,就可以知道它所对应的哈希桶位置,然后就可以访问相应的 entry 元素。
2626

2727
你看,这个查找过程主要依赖于哈希计算,和数据量的多少并没有直接关系。也就是说,不管哈希表里有 10 万个键还是 100 万个键,我们只需要一次计算就能找到相应的键。但是,如果你只是了解了哈希表的 $O(1)$ 复杂度和快速查找特性,那么,当你往 Redis 中写入大量数据后,就可能发现操作有时候会突然变慢了。这其实是因为你忽略了一个潜在的风险点,那就是哈希表的冲突问题和 rehash 可能带来的操作阻塞。
2828

2929
### 为什么哈希表操作变慢了?
3030

3131
当你往哈希表中写入更多数据时,哈希冲突是不可避免的问题。这里的哈希冲突,也就是指,两个 key 的哈希值和哈希桶计算对应关系时,正好落在了同一个哈希桶中。毕竟,哈希桶的个数通常要少于 key 的数量,这也就是说,难免会有一些 key 的哈希值对应到了同一个哈希桶中。
3232

33-
Redis 解决哈希冲突的方式,就是链式哈希。和 JDK7 中的 HahsMap 类似,链式哈希也很容易理解,**就是指同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接**
33+
Redis 解决哈希冲突的方式,就是<mark>链式哈希</mark>。和 JDK7 中的 HahsMap 类似,链式哈希也很容易理解,**就是指同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接**
3434

35-
如下图所示:entry1、entry2 和 entry3 都需要保存在哈希桶 3 中,导致了哈希冲突。此时,entry1 元素会通过一个 `*next` 指针指向 entry2,同样,entry2 也会通过 `*next` 指针指向 entry3。这样一来,即使哈希桶 3 中的元素有 100 个,我们也可以通过 entry 元素中的指针,把它们连起来。这就形成了一个链表,也叫作哈希冲突链。
35+
如下图所示:哈希桶 6 上就有 3 个连着的 entry,也叫作哈希冲突链。
3636

3737
![redis-kv-conflict](https://tva1.sinaimg.cn/large/008i3skNly1gqsfbb5t10j31kx0u0qqw.jpg)
3838

@@ -56,9 +56,11 @@ Redis 解决哈希冲突的方式,就是链式哈希。和 JDK7 中的 HahsMap
5656

5757
好了,到这里,你应该就能理解,Redis 的键和值是怎么通过哈希表组织的了。对于 String 类型来说,找到哈希桶就能直接增删改查了,所以,哈希表的 $O(1)$ 操作复杂度也就是它的复杂度了。但是,对于集合类型来说,即使找到哈希桶了,还要在集合中再进一步操作。
5858

59-
## 一、Redis 的五种基本数据类型和其数据结构
59+
所以集合的操作效率又与集合的底层数据结构有关,接下来我们再说 Redis 的底层数据结构~
60+
6061

6162

63+
## 一、Redis 的五种基本数据类型和其数据结构
6264

6365
由于 Redis 是基于标准 C 写的,只有最基础的数据类型,因此 Redis 为了满足对外使用的 5 种数据类型,开发了属于自己**独有的一套基础数据结构**,使用这些数据结构来实现 5 种数据类型。
6466

@@ -70,15 +72,9 @@ Redis 为了平衡空间和时间效率,针对 value 的具体类型在底层
7072

7173
![](../../../docs/_images/redis/data-type-structure.jpg)
7274

73-
> 安装好 Redis,我们可以使用 `redis-cli` 来对 Redis 进行命令行的操作,当然 Redis 官方也提供了在线的调试器,你也可以在里面敲入命令进行操作:http://try.redis.io/#run
74-
75-
76-
77-
78-
7975
下面我们具体看下各种数据类型的底层实现和操作。
8076

81-
77+
> 安装好 Redis,我们可以使用 `redis-cli` 来对 Redis 进行命令行的操作,当然 Redis 官方也提供了在线的调试器,你也可以在里面敲入命令进行操作:http://try.redis.io/#run
8278
8379
### 1、String(字符串)
8480

@@ -88,17 +84,17 @@ String 类型是二进制安全的。意思是 Redis 的 String 可以包含任
8884

8985
Redis 的字符串是动态字符串,是可以修改的字符串,**内部结构实现上类似于 Java 的 ArrayList**,采用预分配冗余空间的方式来减少内存的频繁分配,如图中所示,内部为当前字符串实际分配的空间 capacity 一般要高于实际字符串长度 len。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是字符串最大长度为 512M。
9086

91-
![redis-string.jpg](http://ww1.sinaimg.cn/large/9b9f09a9ly1g9ypoobef5j20fw04pq2p.jpg)
87+
![redis-string-length](https://tva1.sinaimg.cn/large/008i3skNly1gr1lquo98sj330p0u07ng.jpg)
9288

9389

9490

9591
Redis 没有直接使用 C 语言传统的字符串表示(以空字符结尾的字符数组,以下简称 C 字符串), 而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型, 并将 SDS 用作 Redis 的默认字符串表示。
9692

9793
根据传统, C 语言使用长度为 `N+1` 的字符数组来表示长度为 `N` 的字符串, 并且字符数组的最后一个元素总是空字符 `'\0'`
9894

99-
比如说, 图 2-3 就展示了一个值为 `"Redis"` 的 C 字符串:
95+
比如说, 下图就展示了一个值为 `"Redis"` 的 C 字符串:
10096

101-
![](http://redisbook.com/_images/graphviz-cd9ca0391fd6ab95a2c5b48d5f5fbd0da2db1cab.png)
97+
![c-string](https://tva1.sinaimg.cn/large/008i3skNly1gr1lw2vyjwj335b0tf7cz.jpg)
10298

10399
C 语言使用的这种简单的字符串表示方式, 并不能满足 Redis 对字符串在安全性、效率、以及功能方面的要求
104100

@@ -110,15 +106,15 @@ C 语言使用的这种简单的字符串表示方式, 并不能满足 Redis
110106

111107
和 C 字符串不同, 因为 SDS 在 `len` 属性中记录了 SDS 本身的长度, 所以获取一个 SDS 长度的复杂度仅为 $O(1)$
112108

113-
举个例子, 对于图 2-5 所示的 SDS 来说, 程序只要访问 SDS 的 `len` 属性, 就可以立即知道 SDS 的长度为 `5` 字节:
109+
举个例子, 对于下图所示的 SDS 来说, 程序只要访问 SDS 的 `len` 属性, 就可以立即知道 SDS 的长度为 `5` 字节:
114110

115-
![](http://redisbook.com/_images/graphviz-dbd2f4d49a9f495f18093129393569f93e645529.png)
111+
![redis-sds](https://tva1.sinaimg.cn/large/008i3skNly1gr1ma401ftj32pm0u0x3n.jpg)
116112

117113
通过使用 SDS 而不是 C 字符串, Redis 将获取字符串长度所需的复杂度从 $O(N)$ 降低到了 $O(1)$ , 这确保了获取字符串长度的工作不会成为 Redis 的性能瓶颈
118114

119115
- **缓冲区溢出/内存泄漏**
120116

121-
跟上述问题原因一样,如果执行拼接 or 缩短字符串的操作,如果操作不当就很容易造成上述问题
117+
跟上述问题原因一样,如果执行拼接 or 缩短字符串的操作,操作不当就很容易造成上述问题
122118

123119
- **减少修改字符串带来的内存分配次数**
124120

@@ -221,26 +217,12 @@ Redis hash 是一个键值对集合。KV 模式不变,但 V 是一个键值对
221217

222218
Redis 的字典相当于 Java 语言里面的 HashMap,它是无序字典, 内部实现结构上同 Java 的 HashMap 也是一致的,同样的**数组 + 链表**二维结构。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。
223219

224-
![redis-hash.jpg](http://ww1.sinaimg.cn/large/9b9f09a9ly1g9ypp2rs05j20g307pa9u.jpg)
225-
226220
不同的是,Redis 的字典的值只能是字符串,另外它们 rehash 的方式不一样,因为 Java 的 HashMap 在字典很大时,rehash 是个耗时的操作,需要一次性全部 rehash。Redis 为了高性能,不能堵塞服务,所以采用了渐进式 rehash 策略。
227221

228-
![redis-rehash](https://cdn.jsdelivr.net/gh/Jstarfish/picBed/img/20201116154218.png)
229-
230-
231-
232222
渐进式 rehash 会在 rehash 的同时,保留新旧两个 hash 结构,查询时会同时查询两个 hash 结构,然后在后续的定时任务中以及 hash 操作指令中,循序渐进地将旧 hash 的内容一点点迁移到新的 hash 结构中。当搬迁完成了,就会使用新的 hash 结构取而代之。
233223

234224
当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收。
235225

236-
![](https://cdn.jsdelivr.net/gh/Jstarfish/picBed/img/20201116154311.gif)
237-
238-
239-
240-
hash 结构也可以用来存储用户信息,不同于字符串一次性需要全部序列化整个对象,hash 可以对用户结构中的每个字段单独存储。这样当我们需要获取用户信息时可以进行部分获取。而以整个字符串的形式去保存用户信息的话就只能一次性全部读取,这样就会比较浪费网络流量。
241-
242-
hash 也有缺点,hash 结构的存储消耗要高于单个字符串,到底该使用 hash 还是字符串,需要根据实际情况再三权衡。
243-
244226

245227

246228
#### [字典的实现](http://redisbook.com/preview/dict/datastruct.html "字典的实现")
@@ -285,10 +267,6 @@ Redis 的 Set 是 String 类型的无序集合。它是通过 HashTable 实现
285267

286268
当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。
287269

288-
![img](https://cdn.jsdelivr.net/gh/Jstarfish/picBed/img/20201116154820.gif)
289-
290-
291-
292270

293271

294272
### 5、zset(sorted set:有序集合)
@@ -301,7 +279,7 @@ Redis 正是通过分数来为集合中的成员进行从小到大的排序。zs
301279

302280
zset 中最后一个 value 被移除后,数据结构自动删除,内存被回收。
303281

304-
![img](https://cdn.jsdelivr.net/gh/Jstarfish/picBed/img/20201116154851.gif)
282+
305283

306284
## 二、其他数据类型
307285

@@ -325,7 +303,7 @@ zset 中最后一个 value 被移除后,数据结构自动删除,内存被
325303

326304
接下来我们使用 redis-cli 设置第一个字符,也就是位数组的前 8 位,我们只需要设置值为 1 的位,如上图所示,h 字符只有 1/2/4 位需要设置,e 字符只有 9/10/13/15 位需要设置。值得注意的是位数组的顺序和字符的位顺序是相反的。
327305

328-
```
306+
```sh
329307
127.0.0.1:6379> setbit s 1 1
330308
(integer) 0
331309
127.0.0.1:6379> setbit s 2 1

0 commit comments

Comments
 (0)