上篇文章《Redis数据结构概述》中,了解了常用数据结构。我们知道Redis以高效的方式实现了多种数据结构,因此把Redis看做为数据结构服务器也未尝不可。研究Redis的数据结构和正确、高效使用,对我们的应用程序会大有裨益。接下来的文章中我们逐个深入理解Redis的数据结构和使用场景。

Redis数据结构,我们从Redis暴露给外部使用的数据结构和内部实现的两个角度来学习。

1、暴露给外部使用的数据结构

  • string

  • list

  • hash

  • set

  • sort set

2、内部实现基础结构即底层数据结构

  • dict

  • sds

  • intset

  • ziplist

  • skiplist

  • quicklist

...

先抛出两个问题:Redis是如何组合 "内部实现" 的各种基础数据结构来实现 "暴露给外部使用" 的数据结构? "暴露给外部使用" 的数据结构与"内部实现" 的基础数据结构之间是什么样的关系?

一、dict概述

dict是一个用于维护key和value映射关系的数据结构,与很多语言中的Map或dictionary类似。Redis的一个database中所有key到value的映射,就是使用一个dict来维护的,即保存数据库的键值对。

不过,dict在Redis中被使用的地方还有很多:

1、一个Redis hash结构,当它的field较多时,便会采用dict来存储即Hash底层实现之一(另一种是通过压缩列表实现)。

2、Redis配合使用dict和skiplist来共同维护一个sorted set。

dict本质上是为了解决算法中的查找问题(Searching),一般查找问题的解法分为两个大类:一个是基于各种平衡树,一个是基于哈希表。我们平常使用的各种Map或dictionary,大都是基于哈希表实现的。在不要求数据有序存储,且能保持较低的哈希值冲突概率的前提下,基于哈希表的查找性能能做到非常高效,接近O(1),而且实现简单。

在Redis中,dict也是一个基于哈希表的算法。和传统的哈希算法类似,它采用某个哈希函数从key计算得到在哈希表中的位置,采用拉链法解决冲突,并在装载因子(load factor)超过预定值时自动扩展内存,引发重哈希(rehashing)。

Redis的dict实现最显著的一个特点,就在于它的重哈希。它采用了一种称为增量式重哈希(incremental rehashing)的方法,在需要扩展内存时避免一次性对所有key进行重哈希,而是将重哈希操作分散到对于dict的各个增删改查的操作中去。这种方法能做到每次只对一小部分key进行重哈希,而每次重哈希之间不影响dict的操作。dict之所以这样设计,是为了避免重哈希期间单个请求的响应时间剧烈增加,这与“快速响应时间”的设计原则是相符的。

二、dict数据结构定义

为了实现增量式重哈希(incremental rehashing),dict的数据结构里包含两个哈希表。在重哈希期间,数据从第一个哈希表向第二个哈希表迁移。

我们看下Redis源码中dict.h

/* Hash Tables Implementation.

*

* This file implements in-memory hash tables with insert/del/replace/find/

* get-random-element operations. Hash tables will auto-resize if needed

* tables of power of two in size are used, collisions are handled by

* chaining. See the source code for more information... :)

*

* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>

* All rights reserved.

*

* Redistribution and use in source and binary forms, with or without

* modification, are permitted provided that the following conditions are met:

*

*   * Redistributions of source code must retain the above copyright notice,

*     this list of conditions and the following disclaimer.

*   * Redistributions in binary form must reproduce the above copyright

*     notice, this list of conditions and the following disclaimer in the

*     documentation and/or other materials provided with the distribution.

*   * Neither the name of Redis nor the names of its contributors may be used

*     to endorse or promote products derived from this software without

*     specific prior written permission.

*

* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"

* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE

* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE

* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE

* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR

* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF

* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS

* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN

* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)

* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE

* POSSIBILITY OF SUCH DAMAGE.

*/

dict定义dictEntry、dictType、dictht和dict四个结构体来实现散列表的功能。它们具体定义如下:

1、dictEntry

typedef struct dictEntry {

void *key;

union {

void *val;

uint64_t u64;

int64_t s64;

double d;

} v;

struct dictEntry *next;

} dictEntry;

哈希表节点,保存键值对的结构体。
key:元素的key

v:元素的值,只能存放一个被选中的成员

next:后继节点即下一个键值对节点;

处理哈希碰撞,所有分配到同一索引的元素通过next指针链接起来形成链表,key和v都可以保存多种类型的数据。

从dictEntry的定义我们也可以看出dict通过“拉链法”来解决冲突问题。

2、dictType

typedef struct dictType {

// hash方法,根据关键字计算哈希值

unsigned int (*hashFunction)(const void *key);

// 复制key

void *(*keyDup)(void *privdata, const void *key);

// 复制value

void *(*valDup)(void *privdata, const void *obj);

// 关键字比较方法

int (*keyCompare)(void *privdata, const void *key1, const void *key2);

//  销毁key

void (*keyDestructor)(void *privdata, void *key);

// 销毁value

void (*valDestructor)(void *privdata, void *obj);

} dictType;

redis中通过dictType这样的一个结构用来存储针对不同类型的键值对的处理函数,即定义了字典操作的公共方法。这样对于不同类型的键值对,就可以有不同的处理了。即通过函数指针实现多态。

3、dictht

/* This is our hash table structure. Every dictionary has two of this as we

* implement incremental rehashing, for the old to the new table. */

typedef struct dictht {

dictEntry **table;

unsigned long size;

unsigned long sizemask;

unsigned long used;

} dictht;

dictht哈希表结构,

  • table,散列数组(二级指针)真正存储数据的地方。可以将table看做一个指向数组的指针,而数组就是hash表最基本的结构。通过数组和hash节点中的next指针形成完整的hash表。

  • size,散列数组的长度,通常是2的整数次方。

  • sizemask,hash表大小掩码,用于计算索引,当size非0时为(size-1)

  • used,散列数组中已经被使用的节点数量

4、dict

typedef struct dict {

dictType *type;

void *privdata;

dictht ht[2];

long rehashidx; /* rehashing not in progress if rehashidx == -1 */

int iterators; /* number of iterators currently running */

} dict;

dict,字典的主操作类,对dictht再次包装。

  • type,字典类型。处理函数表,通过其中保存的函数指针对不同类型的数据进行不同的处理,实现多态。

  • privdata,私有数据。

  • ht[2],哈希表,一个字典中有两个哈希表。

  • rehashidx,数据动态迁移时的下标位置。ht[0]中正在rehash的桶的索引,当rehash=-1时,表明此时没有在进行rehash操作。

  • iterators,当前正在使用的迭代器的数量。

5、ictIterator

/* If safe is set to 1 this is a safe iterator, that means, you can call

* dictAdd, dictFind, and other functions against the dictionary even while

* iterating. Otherwise it is a non safe iterator, and only dictNext()

* should be called while iterating. */

typedef struct dictIterator {

dict *d;

long index;

int table, safe;

dictEntry *entry, *nextEntry;

/* unsafe iterator fingerprint for misuse detection. */

long long fingerprint;

} dictIterator;

dictEntry、dictType、dictht和dict四个结构体之间的关系

三、总结

以上对dictEntry、dictType、dictht和dict四个结构体进行了说明,接下了对四个结构体的作用总结:

一个dict由如下若干项组成:

  • 一个指向dictType结构的指针(type)。它通过自定义的方式使得dict的key和value能够存储任何类型的数据。

  • 一个私有数据指针(privdata)。由调用者在创建dict的时候传进来。

  • 两个哈希表(ht[2])。只有在重哈希的过程中,ht[0]和ht[1]才都有效。而在平常情况下,只有ht[0]有效,ht[1]里面没有任何数据。

  • 当前重哈希索引(rehashidx)。如果rehashidx = -1,表示当前没有在重哈希过程中;否则,表示当前正在进行重哈希,且它的值记录了当前重哈希进行到哪一步了。

  • 当前正在进行遍历的iterator的个数。

dictType结构包含若干函数指针,用于dict的调用者对涉及key和value的各种操作进行自定义。这些操作包含:

  • hashFunction,对key进行哈希值计算的哈希算法。

  • keyDup和valDup,分别定义key和value的拷贝函数,用于在需要的时候对key和value进行深拷贝,而不仅仅是传递对象指针。

  • keyCompare,定义两个key的比较操作,在根据key进行查找时会用到。

  • keyDestructor和valDestructor,分别定义对key和value的析构函数。

私有数据指针(privdata)就是在dictType的某些操作被调用时会传回给调用者。

dictht定义一个哈希表的结构,由如下若干项组成:

  • 一个dictEntry指针数组(table)。key的哈希值最终映射到这个数组的某个位置上(对应一个bucket)。如果多个key映射到同一个位置,就发生了冲突,那么就拉出一个dictEntry链表。

  • size:标识dictEntry指针数组的长度。它总是2的指数。

  • sizemask:用于将哈希值映射到table的位置索引。它的值等于(size-1),比如7, 15, 31, 63,等等,也就是用二进制表示的各个bit全1的数字。每个key先经过hashFunction计算得到一个哈希值,然后计算(哈希值 & sizemask)得到在table上的位置。相当于计算取余(哈希值 % size)。

  • used:记录dict中现有的数据个数。它与size的比值就是装载因子(load factor)。这个比值越大,哈希值冲突概率越高。

dictEntry结构,包含k, v和指向链表下一项的next指针。

k是void指针,这意味着它可以指向任何类型。

v是个union,当它的值是uint64_t、int64_t或double类型时,就不再需要额外的存储,这有利于减少内存碎片。当然,v也可以是void指针,以便能存储任何类型的数据。

-eof-

Redis 数据结构之dict的更多相关文章

  1. Redis 数据结构之dict(2)

    本文及后续文章,Redis版本均是v3.2.8 上篇文章<Redis 数据结构之dict>,我们对dict的结构有了大致的印象.此篇文章对dict是如何维护数据结构的做个详细的理解. 老规 ...

  2. redis数据结构存储Dict设计细节(redis的设计与实现笔记)

    说到redis的Dict(字典),虽说算法上跟市面上一般的Dict实现没有什么区别,但是redis的Dict有2个特殊的地方那就是它的rehash(重新散列)和它的字典节点单向链表. 以下是dict用 ...

  3. Redis 数据结构的底层实现 (二) dict skiplist intset

    一.REDIS_INCODING_HT (dict字典,hashtable) dict是一个用于维护key和value映射关系的数据结构.redis的一个database中所有的key到value的映 ...

  4. Redis数据结构详解(2)-redis中的字典dict

    前提知识 字典,又被称为符号表(symbol table)或映射(map),其实简单地可以理解为键值对key-value. 比如Java的常见集合类HashMap,就是用来存储键值对的. 字典中的键( ...

  5. Redis数据结构底层知识总结

    Redis数据结构底层总结 本篇文章是基于作者黄建宏写的书Redis设计与实现而做的笔记 数据结构与对象 Redis中数据结构的底层实现包括以下对象: 对象 解释 简单动态字符串 字符串的底层实现 链 ...

  6. Redis 数据结构与内存管理策略(上)

    Redis 数据结构与内存管理策略(上) 标签: Redis Redis数据结构 Redis内存管理策略 Redis数据类型 Redis类型映射 Redis 数据类型特点与使用场景 String.Li ...

  7. Redis 数据结构与内存管理策略(下)

    Redis 数据结构与内存管理策略(下) 标签: Redis Redis数据结构 Redis内存管理策略 Redis数据类型 Redis类型映射 Redis 数据类型特点与使用场景 String.Li ...

  8. Redis数据结构之intset

    本文及后续文章,Redis版本均是v3.2.8 上篇文章<Redis数据结构之robj>,我们说到redis object数据结构,其有5中数据类型:OBJ_STRING,OBJ_LIST ...

  9. Redis数据结构之robj

    本文及后续文章,Redis版本均是v3.2.8 我们知道一个database内的这个映射关系是用一个dict来维护的.dict的key固定用一种数据结构来表达,这这数据结构就是动态字符串sds.而va ...

随机推荐

  1. 「洛谷1884」「USACO12FEB」过度种植【离散化扫描线】

    题目链接 [洛谷传送门] 题解 矩阵面积的并模板.(请求洛谷加为模板题) 很明显是要离散化的. 我们将矩阵与\(x\)轴平行的两个线段取出来.并且将这两个端点的\(x1\)和\(x2\)进行离散化. ...

  2. jmeter笔记(2)--组件介绍

    1.测试计划 测试计划(Test Plan)是使用JMeter进行测试的起点,它是其它JMeter测试元件的容器. 2.Threads(Users)-线程组 每个测试需求的必备组件,是用来模拟用户并发 ...

  3. Oracle Database 快捷版 安装 连接

    Oracle Database 快捷版 11g 第 2 版 下载地址:http://www.oracle.com/technetwork/cn/database/database-technologi ...

  4. Day047--JS BOM介绍, jQuery介绍和使用

    内容回顾 DOM 文档对象模型(model) 一个模型就是一个对象(属性和方法 面向对象的三大特性:封装 继承 多态) 为了可扩展性 DOM操作 标签属性操作 获取值 getAttribute() 设 ...

  5. utf8mb4的大小写敏感性测试及其修改方法

    utf8mb4的大小写敏感性测试及其修改方法 utf8mb4_ unicode_ ci 与 utf8mb4_ general_ ci 如何选择字符除了需要存储,还需要排序或比较大小,涉及到与编码字符集 ...

  6. 版本控制工具之git

    git存储区域详解 命令快速总结 初始化 git init 当前文件夹初始化 代码提交 git add file/. 自动检测工作区修改的内容提交到暂存区 git status 查看当前文件夹工作区的 ...

  7. javax.websocket.DeploymentException: Multiple Endpoints may not be deployed to the same path [/websocket/{sid}] : existing endpoint was class com.sanyi.qibaobusiness.framework.webSocket.WebSocketServe

    报错: javax.websocket.DeploymentException: Multiple Endpoints may not be deployed to the same path [/w ...

  8. numpy&pandas基础

    numpy基础 import numpy as np 定义array In [156]: np.ones(3) Out[156]: array([1., 1., 1.]) In [157]: np.o ...

  9. IScroll某些手机下不触发ScrollEnd问题处理

    同样是微信7.0,看起来内核都是x5内核,两款不同的手机,一个有问题,一个没有问题. IScroll在问题手机下会出现快速拨动时候不触发ScrollEnd事件现象,轻点一次才会触发,解决办法 docu ...

  10. PHP中的常用数组操作方法

    一.数组操作的基本函数 数组的键名和值array_values($arr);  获得数组的值array_keys($arr);  获得数组的键名array_flip($arr);  数组中的值与键名互 ...