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
执行以下步骤:
-
计算哈希值:调用
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> | 多线程安全字典 |
相关文章:
C# Dictionary的实现原理
在 C# 中,Dictionary<TKey, TValue> 是一个基于哈希表(Hash Table)实现的键值对集合。它提供了高效的插入、删除和查找操作,平均时间复杂度接近 O(1)。下面是 Dictionary 的核心实现原理: 1. Dictionary 的核心数…...
学习笔记-人脸识别相关编程基础
通过编程实现人脸识别功能,需要掌握一定的技术基础,包括编程语言、图像处理、机器学习以及相关的库和框架: 1. 编程语言 Python:Python 是实现人脸识别最常用的语言之一,因为它有大量的库和框架支持,如 Op…...

BUU37 [DASCTF X GFCTF 2024|四月开启第一局]web1234【代码审计/序列化/RCE】
Hint1:本题的 flag 不在环境变量中 Hint2:session_start(),注意链子挖掘 题目: 扫描出来www.zip class.php <?phpclass Admin{public $Config;public function __construct($Config){//安全获取基…...

(五)Spring Boot学习——spring security +jwt使用(前后端分离模式)
一定要熟悉spring security原理和jwt无状态原理,理解了才知道代码作用。 在 Spring Security JWT 认证流程中,通常的做法是: 用户提交用户名和密码Spring Security 认证管理器 (AuthenticationManager) 进行认证如果认证成功,生…...

Java中使用EasyExcel
Java中使用EasyExcel 文章目录 Java中使用EasyExcel一:EasyExcel介绍1.1、核心函数导入数据导出数据 1.2、项目实际应用导入数据导出数据 1.3、相关注解ExcelProperty作用示例 二:EasyExcel使用2.1、导入功能2.2、导出功能 三:EasyExcel完整代…...
前沿科技改变生活新趋势
纳米技术在电子设备制造中的应用越来越广泛。这种技术能够帮助制造更小、更快、更耐用的电子产品。 举个例子,手机的处理器是其核心部件。随着纳米技术的进步,现在的处理器比以前小得多,但功能却更强。这样不仅让手机变得更轻薄,…...
不到一个月,SQLite 3.49.0来了
距离 SQLite 3.48.0 发布不到一个月,SQLite 开发团队于 2025 年 2 月 6 日发布了 SQLite 3.49.0 版本。这更新速度的确让人感动,那么这个版本又有哪些更新呢? 查询优化器 新版本改进了自动索引(query-time index)优化…...
Android车机DIY开发之软件篇(十四)编译i.mx8mplus官方kernel
1.下载 下载地址 2.安装依赖 sudo apt-get update sudo apt-get install build-essential git libncurses5-dev libssl-dev bc sudo apt-get install gcc-aarch64-linux-gnu export CROSS_COMPILEaarch64-linux-gnu- 3.配置 make ARCHarm64 defconfig 4.编译 make ARCHa…...

Mac上搭建宝塔环境并部署PHP项目
安装Docker Desktop》搭建Centos版本的宝塔环境》部署PHP项目 1. 下载Docker for mac 软件:https://www.docker.com/ 或使用终端命令:brew install --cask --appdir/Applications docker 2. 使用命令安装宝塔环境的centos7系统: docker pul…...

3.3.3 VO-O语法- 语法算子(二)
循环遍历 由于VO语言是面向数据集的,其所有隐含的语义中都已经带有了遍历并计算的数据逻辑。因此,VO语言只提供了一种支持循环语法的算子--Loop算子。 Loop算子 Loop算子是一个容器算子,其可以实现对其内部子流程的循环迭代运行。但Loop算…...
安装 Ollama 需要哪些步骤?(windows+mac+linux+二进制+Docker)
安装 Ollama 的步骤根据操作系统不同会有所差异,以下是针对不同操作系统的详细安装指南: Windows 系统 下载安装包:访问 Ollama 官方下载页面,下载适用于 Windows 的安装程序 OllamaSetup.exe。运行安装程序:双击下载的安装包,按照提示完成安装。默认安装路径为 C:\User…...

HCIA项目实践--静态路由的综合实验
八 静态路由综合实验 (1)划分网段 # 192.168.1.0 24#分析:每个路由器存在两个环回接口,可以把两个环回接口分配一个环回地址,所以是四个环回,一个骨干,这样分配,不会出现路由黑洞#19…...

Electron视图进程和主进程通讯
快速创建基于vue的electron项目:quick-start/create-electron - npm 视图线程也就index.html是无法直接访问这个api的(如果没有开启视图层访问nodejs的功能,现在几乎没法直接开启,开启了一堆警告提示) 所以需要通过r…...

Vript-Hard——一个基于高分辨率和详细字幕的视频理解算法
一、概述 多模态学习的最新进展促进了对视频理解和生成模型的研究。随之而来的是,对高分辨率视频和详细说明所建立的高质量数据集的需求激增。然而,由于时间因素的影响,视频与文本的配对不像图像那样容易。准备视频和文本配对是一项困难得多…...
react脚手架搭建react项目使用scss
1.create-react-app 创建的项目,webpack配置默认是隐藏的 ,如果要查看 或修改用npm run eject命令,因为create-react-app脚手架默认已经配置了scss、sass所以不用改webpack配置。如果用less 就需要自己添加配置 2.如果直接使用scss的文件会直接报错&…...

Vue.js 状态管理库Pinia
Pinia Pinia :Vue.js 状态管理库Pinia持久化插件-persist Pinia :Vue.js 状态管理库 Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。 要使用Pinia ,先要安装npm install pinia在main.js中导入Pinia 并使用 示例…...
【Stable Diffusion部署至GNU/Linux】安装流程
以下是安装Stable Diffusion的步骤,以Ubuntu 22.04 LTS为例子。 显卡与计算架构介绍 CUDA是NVIDIA GPU的专用并行计算架构 技术层级说明CUDA Toolkit提供GPU编译器(nvcc)、数学库(cuBLAS)等开发工具cuDNN深度神经网络加速库(需单独下载)GPU驱动包含CUDA Driver(需与CUDA …...

【C/C++算法】从浅到深学习---滑动窗口(图文兼备 + 源码详解)
绪论:冲击蓝桥杯一起加油!! 每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry” 绪论: 本章是算法训练的第二章----滑动窗口,它的本质是双指针算法的衍生所以我将…...

计算机毕业设计SpringBoot+Vue.js房源推荐系统 房价预测 房源大数据分析可视化(源码+文档+运行视频+讲解视频)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
开源机器人+具身智能 解决方案+AI
开源机器人、具身智能(Embodied Intelligence)以及AI技术的结合,可以为机器人领域带来全新的解决方案。以下是这一结合的可能方向和具体方案: 1. 开源机器人平台 开源机器人平台为开发者提供了灵活的基础架构,可以在此基础上结合具身智能和AI技术。以下是一些常用的开源机…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

联邦学习带宽资源分配
带宽资源分配是指在网络中如何合理分配有限的带宽资源,以满足各个通信任务和用户的需求,尤其是在多用户共享带宽的情况下,如何确保各个设备或用户的通信需求得到高效且公平的满足。带宽是网络中的一个重要资源,通常指的是单位时间…...

vue3 手动封装城市三级联动
要做的功能 示意图是这样的,因为后端给的数据结构 不足以使用ant-design组件 的联动查询组件 所以只能自己分装 组件 当然 这个数据后端给的不一样的情况下 可能组件内对应的 逻辑方式就不一样 毕竟是 三个 数组 省份 城市 区域 我直接粘贴组件代码了 <temp…...
JS的传统写法 vs 简写形式
一、条件判断与逻辑操作 三元运算符简化条件判断 // 传统写法 let result; if (someCondition) {result yes; } else {result no; }// 简写方式 const result someCondition ? yes : no;短路求值 // 传统写法 if (condition) {doSomething(); }// 简写方式 condition &…...