网站推广专业术语装潢设计公司排行
在 C# 中,Dictionary<TKey, TValue> 是一个基于哈希表(Hash Table)实现的键值对集合。它提供了高效的插入、删除和查找操作,平均时间复杂度接近 O(1)。下面是 Dictionary 的核心实现原理:
1. Dictionary 的核心数据结构
C# 的 Dictionary<TKey, TValue> 主要由以下几个部分组成:
- 数组(buckets):存储哈希桶(Bucket)的索引。
 - 数组(entries):存储键值对(哈希表中的实际数据)。
 - 哈希函数(GetHashCode):将键映射到哈希桶。
 - 碰撞解决(拉链法):采用 链地址法(Chaining with Linked List)。
 - 负载因子(Load Factor):决定何时扩容,默认为 0.75。
 
数据结构
private int[] buckets;         // 哈希桶数组,存储 entry 的索引
private Entry[] entries;       // 存储实际的键值对
private int count;             // 当前存储的元素个数
private int freeList;          // 指向空闲 entry(删除后形成的空位)
private int freeCount;         // 空闲 entry 的数量private struct Entry
{public int hashCode;       // 计算出的哈希值public int next;           // 指向下一个冲突的元素(-1 表示无冲突)public TKey key;           // 键public TValue value;       // 值
} 
2. Dictionary 的主要操作
(1)添加元素
当调用 dictionary.Add(key, value) 时,Dictionary 执行以下步骤:
-  
计算哈希值:调用
key.GetHashCode()计算哈希值hashCode。 -  
计算索引:对哈希值取模,
index = hashCode % buckets.Length,确定应该存放的桶。 -  
检查冲突:
- 如果该桶为空,则直接存储。此时count字段记录字典中元素个数。令bucket[index] = count,然后将内容存储到Entry[count]中,随后count++。
 - 如果发生哈希冲突,则使用链地址法存储,即将 
entries中的next指向旧的Entry,形成链表。 
 -  
扩容(如有必要):
- 如果 
count > capacity * 0.75,则触发扩容,通常是 2 倍扩容。 
public void Add(TKey key, TValue value)
{
int hashCode = key.GetHashCode() & 0x7FFFFFFF; // 计算哈希值int index = hashCode % buckets.Length; // 计算桶的索引// 处理哈希冲突:如果桶为空,直接插入if (buckets[index] == -1){buckets[index] = count; // 将当前元素插入桶数组entries[count] = new Entry { hashCode = hashCode, key = key, value = value, next = -1 };count++;}else{// 发生哈希冲突,遍历链表int current = buckets[index];while (current >= 0){Entry entry = entries[current];if (entry.hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entry.key, key)){entries[current].value = value; // 如果找到相同的键,更新值return;}current = entry.next; // 查找下一个节点}// 如果没有找到,插入新的元素到链表头部entries[count] = new Entry { hashCode = hashCode, key = key, value = value, next = buckets[index] };buckets[index] = count; // 更新桶的索引count++;}// 扩容检查if (count > buckets.Length * 0.75){Resize(); // 扩容}}
 - 如果 
 
(2)查找元素
当调用 dictionary[key] 或 TryGetValue(key, out value) 时:
-  
计算哈希值:
hashCode = key.GetHashCode()。 -  
计算索引:
index = hashCode % buckets.Length。 -  
遍历链表
:
- 如果桶中第一个元素匹配,则直接返回。
 - 如果有哈希冲突(next != -1),遍历 
entries直到找到key。 
 
(3)删除元素
当调用 dictionary.Remove(key) 时:
- 计算哈希值,找到哈希桶索引。
 - 遍历链表,找到匹配的 
Entry。 - 更新链表指针: 
- 如果是第一个元素,则更新 
buckets指向next位置。 - 如果在链表中,则调整 
next以跳过该元素。 
 - 如果是第一个元素,则更新 
 - 回收 entry: 
- 将 
entry记录到freeList,方便后续使用。 
 - 将 
 
(4)扩容机制
当 count >= capacity * 0.75 时,Dictionary 会进行 2 倍扩容:
- 创建更大的 buckets 和 entries 数组(通常是 2 倍大小)。
 - 重新计算索引,重新分配 
buckets和entries。 - 重新插入所有 Entry,因为 
hashCode % newCapacity结果不同,所有键值对需要重新计算索引。 
3. Dictionary 的碰撞处理
C# 的 Dictionary 采用 链地址法 处理哈希冲突:
entries形成链表,每个Entry记录next指向同一个桶的下一个Entry。- 遍历链表时,检查 
hashCode和key是否匹配。 
示例假设 buckets 长度为 5,并插入以下键值对:
dict.Add(1, "Apple");
dict.Add(6, "Banana"); // 6 % 5 == 1,与 1 发生哈希冲突 
哈希桶结构如下:
buckets: [ -1,  0 -> 1, -1, -1, -1 ]   // index 1 存储链表 (1 → 6)
entries: 0: { hashCode=1, key=1, value="Apple", next=1 }1: { hashCode=6, key=6, value="Banana", next=-1 } 
查找 dict[6] 时:
- 计算 
index = 6 % 5 = 1。 - 遍历链表: 
entries[0](key=1) 不匹配,继续遍历next=1。entries[1](key=6) 匹配,返回"Banana"。
 
4. Dictionary 的优缺点
优点
- 查找快:平均时间复杂度 O(1)。
 - 插入删除快:平均时间复杂度 O(1)。
 - 自动扩容:避免手动管理大小。
 
缺点
- 内存占用大:数组 + 链表可能浪费额外空间。
 - 哈希碰撞影响性能:冲突越多,查找速度降至 O(n)。
 - 不保证顺序:
Dictionary无序存储键值对。 
5. Dictionary 的替代方案
| 数据结构 | 适用场景 | 
|---|---|
SortedDictionary<TKey, TValue> | 需要按键排序(基于 红黑树,O(log n)) | 
SortedList<TKey, TValue> | 适用于小数据量,查找快但插入慢 | 
HashSet<T> | 仅存储键,不存储值 | 
ConcurrentDictionary<TKey, TValue> | 多线程安全字典 | 
