字典实现

本文介绍了Redis中字典的数据结构及其实现原理,包括哈希表的节点定义、字典类型的函数集合、渐进式rehash机制等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字典在 Redis 中的应用相当广泛,如 Redis 的数据库、Hash 类型等的底层实现都用到了字典。
Redis 的字典使用了哈希表,其中可以包含多个哈希表节点,每个节点就保存了字典中的一个键值对。这两者的结构定义分别如下:

typedef struct dictht{
dictEntry **table; // 哈希表节点数组
unsigned long size; // 哈希表节点数组大小
unsigned long sizemask; // 哈希表大小掩码,用于计算索引值,总是等于 size-1
unsigned long used; // 该哈希表已有节点的数量
}dictht;

typedef struct dictEntry{
void *key; // 键
union{ // 值
void *val;
uint64_t u64;
int64_t s64;
}v;
struct dictEntry *next; // 指向下一个哈希表节点,形成链表
}dictEntry;

从 dictEntry 结构的定义可以看出,键值对中的值可以是一个指针,或者是一个 uint64_t 类型的整数,又或者是一个 int64_t 类型的整数。另外,其中的 next 属性是指向另一个具有相同哈希值的节点的指针,用以避免键冲突。
哈希表的定义给出后,接下来再看看 Redis 中的字典的结构表示。

typedef struct dict{
dictType *type; // 类型特定函数
void *privdata; // 私有数据
dictht ht[2]; // 哈希表
int rehashidx; // 渐进式 rehash 索引,当没进行 rehash 时,值为 -1
}dict;

typedef struct dictType{
unsigned int (*hashFunction)(const void *key); // 计算哈希值的函数
void *(*keyDup)(void *privdata, const void *key); // 复制键的函数
void *(*valDup)(void *privdata, const void *obj); // 复制值的函数
void (*keyDestructor)(void *privdata, void *key); // 销毁键的函数
void (*valDestructor)(void *privdata, void *obj); // 销毁值的函数
int (*keyCompare)(void *privdata, const void *key1, const void *key2); // 对比值的函数
}dictType;

dict 结构中的 type 属性是一个指向 dictType 结构的指针,该结构保存了一组用于操作特定类型键值对的函数,以便 Redis 为不同用途的字典设置不同的类型特定函数。privdata 属性中则保存了需要传给那些类型特定函数的可选参数。这两个属性主要是为创建多态字典,针对不同类型的键值对而设置的。ht 属性是一个包含两个 dictht 哈希表的数组。一般情况下只使用 ht[0] 哈希表,ht[1] 哈希表只会在对 ht[0] 哈希表进行 rehash(即重新对 ht[0] 哈希表中的节点进行哈希值计算) 时使用。属性 rehashidx 也与 rehash 有关,它记录了 rehash 目前的进度。如果目前没有进行 rehash,它的值就为 -1。
下图是一个没有进行 rehash 时的普通状态下的字典的示例。
[img]http://dl2.iteye.com/upload/attachment/0130/4901/119cb498-3cc2-3136-aedd-81e51c3c65fe.png[/img]
当要添加一个新的键值对到字典中时,Redis 会先根据键值对的键计算出哈希值和索引值,然后将该包含该键值对的哈希表节点放到哈希数组的指定索引上面。计算方式大致如下:
hash = dict->type->hashFunction(key); # 使用字典设置的哈希函数计算哈希值
index = hash & dict->ht[x].sizemask; # 根据情况,ht[x] 可以是 ht[0] 或 ht[1]
在键发生冲突时,Redis 使用了链地址法来处理,即利用哈希表节点结构 dictEntry 中 的 next 指针来指向具有相同索引的节点。不过为了速度考虑,程序总是将新节点添加到链表的表头位置(复杂的为 O(1)),排在其他已有节点的前面。
接下来再来说说字典的 rehash。
由于哈希表中保存的键值对会随着操作的不断执行而逐渐地增多或减少,所以为了让哈希表的负载因子(load factor)维持在一个合理的范围内,Redis 需要在必要的时刻对哈希表的大小进行相应的扩展或收缩。这可以通过执行 rehash 操作来完成。大致步骤如下:
1、根据要执行的操作以及 ht[0] 当前已包含的键值对的数量为字典的 ht[1] 哈希表分配空间:
a、如果执行的是扩展操作,则 ht[1] 的大小将为第一个大于等于 ht[0].used * 2 并且是 2 的 n 次幂的数。
b、如果执行的是收缩操作,则 ht[1] 的大小将是第一个大于等于 ht[0].used 并且是 2 的 n 次幂的数。
2、将 ht[0] 中的所有键值对 rehash 到 ht[1] 上面。
3、迁移完 ht[0] 中的键值对后,释放 ht[0],再将 ht[1] 设置为 ht[0],并新建一个空白哈希表作为 ht[1],以便为下一次 rehash 做准备。
那么 Redis 如何知道在什么时候对哈希表进行扩展或收缩呢?
总的来说,当程序满足以下任意一个条件时,Redis 将会自动开始进行扩展操作:
1、服务器目前没有在执行 BGSAVE 命令或者 BGREWRITEAOF 命令,并且哈希表的负载因子大于等于 1。
2、服务器目前正在执行 BGSAVE 命令或者 BGREWRITEAOF 命令,并且哈希表的负载因子大于等于 5。
其中,哈希表的负载因子通过下面公式计算:
load_factor = ht[0].used / ht[0].size; # 即:哈希表已有节点数 / 哈希表大小
另一方面,当哈希表的负载因子小于 0.1 时,程序会自动开始对哈希表执行收缩操作。
另外需要指出的是,在对哈希表进行扩展或收缩时,将 ht[0] 中的键值对 rehash 到 ht[1] 中的这个动作并非是一次性、集中式地完成的,而是分多次、渐进式地完成的。这样做的原因是为了避免在 ht[0] 中的键值对较多时,如果一次性将这些键值对全部迁移到 ht[1] 的话,可能会导致服务器在一段时间内停止对外服务。
渐进式 rehash 的详细步骤如下:
1、为 ht[1] 分配空间。
2、将 dict 结构中的索引计数器字段 rehashidx 的值设置为 0,表示 rehash 开始。
3、在 rehash 进行期间,每次对字典执行添加、删除、查找或者更新操作时,除了执行指定的操作,程序还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1],然后将 rehashidx 的值增 1。在这个过程中,字典的删除、查找和更新操作会依次在 ht[0] 和 ht[1] 中查找对应的键,而新添加到字典的键值对会一律保存到 ht[1] 中,以保证 ht[0] 中包含的键值对数量只减不增,能随着 rehash 操作的进行而最终变成空表。
4、随着字典操作的不断执行,ht[0] 的所有键值对会在某个时刻被 rehash 到 ht[1] 上,这时再将 rehashidx 属性的值设为 -1,表示 rehash 操作已完成。

参考书籍:《Redis 设计与实现》第四章——字典。
<think>好的,我现在需要帮助用户了解如何在Python中使用字典实现某些功能,并且需要结合提供的引用内容来生成相关问题和回答。首先,我需要仔细分析用户的查询和提供的引用资料。 用户的问题是关于如何用Python字典实现具体功能,并请求使用示例。他们提供的四个引用分别涉及字典视图、item()方法遍历字典、四则运算用字典实现,以及字典的基本操作和例题。我需要将这些引用内容整合到回答中,确保覆盖用户的需求。 首先,我应该回顾每个引用的内容。引用1提到字典视图,包括键、值和元素的动态视图,这可能用于展示如何实时获取字典键值对。引用2讨论使用item()方法遍历字典,这可能作为遍历字典的一个有效方法。引用3提到用字典实现四则运算,这可能是一个具体的应用示例,比如将运算符作为键,对应的函数作为值。引用4提到了返回字典的值信息,可能与获取值或处理值有关。 接下来,我需要确定如何组织这些信息。用户需要具体的功能示例,因此我应该分点列出不同的应用场景,每个场景结合一个或多个引用,并给出代码示例。例如,可以包括动态同步的字典视图、遍历字典实现四则运算、数据统计等。 然后,我需要确保每个功能点都正确引用了对应的资料。比如,在讲解字典视图时引用[^1],在遍历字典时引用[^2],四则运算引用[^3],数据统计可能引用[^4]中的值操作部分。 另外,用户要求生成相关问题,我需要根据内容提出相关问题,比如字典视图的动态特性、其他遍历方法、四则运算的扩展、字典的其他应用等。 需要注意的是,回答的结构要清晰,使用中文,并且代码示例要正确,符合LaTeX格式要求。例如,行内公式用$...$,独立公式用$$...$$,但在这个问题中可能较少涉及数学公式,但代码块需要用反引号包裹,并标明语言为Python。 现在,我需要检查是否有遗漏的功能点,或者是否有引用中的信息未被利用。例如,引用4提到了返回字典的值信息,可能在数据统计或处理值时使用values()方法,这部分可以作为一个示例。 最后,确保生成的相关问题与内容相关,并且能够引导用户进一步探索字典的不同应用和特性。</think>### 如何用Python字典实现具体功能? Python字典是一种高效的键值对数据结构,广泛应用于数据映射、动态配置和快速查询等场景。以下是几个具体功能实现示例: --- #### 1. **动态同步的字典视图(引用) 字典视图(`keys()`、`values()`、`items()`)会实时反映字典的修改,适合需要动态监控数据的场景。 ```python data = {"name": "Alice", "age": 30} keys = data.keys() values = data.values() data["city"] = "Beijing" # 动态添加键值对 print(list(keys)) # 输出: ['name', 'age', 'city'] print(list(values)) # 输出: ['Alice', 30, 'Beijing'] ``` --- #### 2. **遍历字典键值对(引用[^2]) 使用`items()`方法遍历字典,可直接获取键和值: ```python scores = {"Math": 90, "English": 85, "History": 78} for subject, score in scores.items(): print(f"科目: {subject}, 分数: {score}") ``` --- #### 3. **实现四则运算映射(引用[^3]) 将运算符作为键,对应的函数作为值,实现动态计算: ```python import operator operations = { "+": operator.add, "-": operator.sub, "*": operator.mul, "/": operator.truediv } a, b = 10, 5 for op in operations: result = operations[op](a, b) print(f"{a} {op} {b} = {result}") # 输出: 10 + 5 = 15, 10 - 5 = 5, ... ``` --- #### 4. **数据统计与分组(引用[^4]) 通过字典统计词频或分组数据: ```python text = "apple banana apple orange banana" words = text.split() word_count = {} for word in words: word_count[word] = word_count.get(word, 0) + 1 print(word_count) # 输出: {'apple': 2, 'banana': 2, 'orange': 1} ``` --- #### 5. **配置管理(引用) 用字典存储程序配置参数,便于动态修改: ```python config = { "debug_mode": True, "max_connections": 100, "timeout": 30 } if config["debug_mode"]: print("调试模式已开启") ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值