当前位置: 首页 > news >正文

C# Dictionary的实现原理

在 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 执行以下步骤:

  1. 计算哈希值:调用 key.GetHashCode() 计算哈希值 hashCode

  2. 计算索引:对哈希值取模,index = hashCode % buckets.Length,确定应该存放的桶。

  3. 检查冲突:

    • 如果该桶为空,则直接存储。此时count字段记录字典中元素个数。令bucket[index] = count,然后将内容存储到Entry[count]中,随后count++。
    • 如果发生哈希冲突,则使用链地址法存储,即将 entries 中的 next 指向旧的 Entry,形成链表。
  4. 扩容(如有必要):

    • 如果 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) 时:

  1. 计算哈希值hashCode = key.GetHashCode()

  2. 计算索引index = hashCode % buckets.Length

  3. 遍历链表

    • 如果桶中第一个元素匹配,则直接返回。
    • 如果有哈希冲突(next != -1),遍历 entries 直到找到 key

(3)删除元素

当调用 dictionary.Remove(key) 时:

  1. 计算哈希值,找到哈希桶索引。
  2. 遍历链表,找到匹配的 Entry
  3. 更新链表指针:
    • 如果是第一个元素,则更新 buckets 指向 next 位置。
    • 如果在链表中,则调整 next 以跳过该元素。
  4. 回收 entry:
    • entry 记录到 freeList,方便后续使用。

(4)扩容机制

count >= capacity * 0.75 时,Dictionary 会进行 2 倍扩容

  1. 创建更大的 buckets 和 entries 数组(通常是 2 倍大小)。
  2. 重新计算索引,重新分配 bucketsentries
  3. 重新插入所有 Entry,因为 hashCode % newCapacity 结果不同,所有键值对需要重新计算索引。

3. Dictionary 的碰撞处理

C# 的 Dictionary 采用 链地址法 处理哈希冲突:

  • entries 形成链表,每个 Entry 记录 next 指向同一个桶的下一个 Entry
  • 遍历链表时,检查 hashCodekey 是否匹配。

示例假设 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] 时:

  1. 计算 index = 6 % 5 = 1
  2. 遍历链表:
    • 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>多线程安全字典

http://www.lryc.cn/news/536464.html

相关文章:

  • 学习笔记-人脸识别相关编程基础
  • BUU37 [DASCTF X GFCTF 2024|四月开启第一局]web1234【代码审计/序列化/RCE】
  • (五)Spring Boot学习——spring security +jwt使用(前后端分离模式)
  • Java中使用EasyExcel
  • 前沿科技改变生活新趋势
  • 不到一个月,SQLite 3.49.0来了
  • Android车机DIY开发之软件篇(十四)编译i.mx8mplus官方kernel
  • Mac上搭建宝塔环境并部署PHP项目
  • 3.3.3 VO-O语法- 语法算子(二)
  • 安装 Ollama 需要哪些步骤?(windows+mac+linux+二进制+Docker)
  • HCIA项目实践--静态路由的综合实验
  • Electron视图进程和主进程通讯
  • Vript-Hard——一个基于高分辨率和详细字幕的视频理解算法
  • react脚手架搭建react项目使用scss
  • Vue.js 状态管理库Pinia
  • 【Stable Diffusion部署至GNU/Linux】安装流程
  • 【C/C++算法】从浅到深学习---滑动窗口(图文兼备 + 源码详解)
  • 计算机毕业设计SpringBoot+Vue.js房源推荐系统 房价预测 房源大数据分析可视化(源码+文档+运行视频+讲解视频)
  • 开源机器人+具身智能 解决方案+AI
  • 通过 VBA 在 Excel 中自动提取拼音首字母
  • 华硕笔记本怎么一键恢复出厂系统_华硕笔记本一键恢复出厂系统教程
  • Ubuntu 如何安装Snipaste截图软件
  • 【离散数学上机】T235,T236
  • 【Android开发】安卓手机APP使用机器学习进行QR二维码识别(完整工程资料源码)
  • 【油猴脚本/Tampermonkey】DeepSeek 服务器繁忙无限重试(20250214优化)
  • 为deepseek搭建本地页面
  • 详解df -h命令
  • 虚拟环境测试部署应用
  • CentOS本机配置为时间源
  • 蓝桥杯备赛 Day14 素数环