Dictionary 学习笔记
一、基础概念与用法
1.1 什么是 Dictionary?
Dictionary<TKey, TValue> 是 C# 中非常常用的键值对集合,属于 System.Collections.Generic 命名空间。
-
✅ 特点:
- 每个元素由一个键(Key) 和一个值(Value) 组成。
- 键必须唯一,不能重复;值可以重复。
- 查询速度快(基于哈希表实现)。
- 类似于其他语言中的“Map”或“Hash”。
💡 类比理解:想象一个电话簿 —— “姓名”是 Key,“电话号码”是 Value,通过姓名快速找到号码。
1.2 如何定义和初始化?
using System;
using System.Collections.Generic;
// 定义一个字典:Key 是 string,Value 是 int
Dictionary<string, int> studentScores = new Dictionary<string, int>();
// 初始化时赋值(C# 6+ 推荐写法)
var scores = new Dictionary<string, int>
{
{ "张三", 85 },
{ "李四", 92 },
{ "王五", 78 }
};
1.3 常用方法与操作
✅ 添加元素
scores.Add("赵六", 90); // 添加新键值对
// 或者直接赋值(如果键已存在会覆盖!)
scores["赵六"] = 95; // 覆盖原值
⚠️ 注意:
Add()方法如果键已存在会抛出异常!推荐使用TryAdd()或直接赋值。
✅ 获取元素
int score = scores["张三"]; // 直接通过键获取值(若键不存在会报错!)
// 更安全的做法:使用 TryGetValue
if (scores.TryGetValue("张三", out int foundScore))
{
Console.WriteLine($"张三的分数是:{foundScore}");
}
else
{
Console.WriteLine("找不到该学生!");
}
✅ 修改与删除
// 修改值
scores["张三"] = 88;
// 删除指定键
scores.Remove("李四");
// 清空所有元素
scores.Clear();
✅ 遍历字典
foreach (KeyValuePair<string, int> kvp in scores)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
// 或者使用解构语法(C# 7+)
foreach (var (name, score) in scores)
{
Console.WriteLine($"{name}: {score}");
}
✅ 判断是否包含键或值
bool hasZhangSan = scores.ContainsKey("张三"); // true
bool has90 = scores.ContainsValue(90); // false(已被覆盖)
二、进阶知识点
2.1 Dictionary vs 其他集合对比
| 集合类型 | 特点 |
|---|---|
Dictionary<TKey, TValue> |
键值对结构,查询快,键唯一,适合查找场景 |
List<T> |
有序列表,按索引访问,适合顺序遍历或频繁插入/删除中间元素 |
HashSet<T> |
无序、无重复元素,适合去重和快速判断是否存在 |
SortedDictionary |
保持按键排序,查询稍慢但遍历时有序(适合需要排序的场景) |
✅ 选择建议:
- 需要“通过名字找数据” → 选
Dictionary- 需要“保存一堆不重复的名字” → 选
HashSet- 需要“按添加顺序或排序展示” → 选
List或SortedDictionary
2.2 性能特点
- 🔍 查找性能:平均 O(1),非常快!因为底层是哈希表。
- 📈 插入/删除性能:平均 O(1),但在哈希冲突严重时可能退化。
- ⚖️ 内存开销:比数组或 List 略高,因为要存储键和哈希信息。
📌 提示:如果你知道最终容量,可以提前指定容量减少扩容开销:
var dict = new Dictionary<string, int>(100); // 预分配空间
2.3 线程安全问题
❗ 重要提醒:
Dictionary<TKey, TValue>不是线程安全的!
多个线程同时读写会导致不可预知错误(如 InvalidOperationException)。
解决方案:
✅ 方案一:手动加锁
private static readonly object _lock = new object();
lock (_lock)
{
scores["张三"] = 85;
}
✅ 方案二:使用线程安全版本 ConcurrentDictionary
using System.Collections.Concurrent;
var concurrentDict = new ConcurrentDictionary<string, int>();
concurrentDict.TryAdd("张三", 85);
concurrentDict["李四"] = 90; // 支持直接赋值
💡
ConcurrentDictionary是专门为多线程设计的,内部做了并发控制,推荐在多线程环境中使用。
2.4 自定义键类型注意事项
如果键是自定义类,必须重写 Equals() 和 GetHashCode() 方法,否则可能无法正确查找!
public class Student
{
public string Name { get; set; }
public int Id { get; set; }
public override bool Equals(object obj)
{
if (obj is Student other)
return Id == other.Id && Name == other.Name;
return false;
}
public override int GetHashCode()
{
return HashCode.Combine(Id, Name);
}
}
📌 否则可能出现:明明对象相同,却查不到的情况!
三、实际工作中的使用场景
3.1 场景一:缓存数据(提升性能)
比如缓存用户信息,避免重复查询数据库:
private static readonly Dictionary<int, User> _userCache = new();
public User GetUser(int userId)
{
if (!_userCache.TryGetValue(userId, out User user))
{
user = LoadUserFromDatabase(userId); // 从数据库加载
_userCache[userId] = user; // 缓存起来
}
return user;
}
✅ 优点:首次加载后,后续访问几乎瞬间返回,大幅提升响应速度。
3.2 场景二:配置管理 / 映射关系
比如根据国家代码映射国家名称:
var countryMap = new Dictionary<string, string>
{
{ "CN", "中国" },
{ "US", "美国" },
{ "JP", "日本" }
};
string name = countryMap["CN"]; // 输出:中国
✅ 适合处理“枚举型”或“映射型”数据,清晰直观。
3.3 场景三:统计频率(计数器)
比如统计单词出现次数:
var wordCount = new Dictionary<string, int>();
string[] words = { "apple", "banana", "apple", "orange", "banana", "apple" };
foreach (string word in words)
{
wordCount[word] = wordCount.GetValueOrDefault(word, 0) + 1;
}
foreach (var kvp in wordCount)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}次");
}
✅
GetValueOrDefault(key, defaultValue)是处理“键不存在时默认值”的好帮手!
3.4 场景四:状态机 / 事件分发
比如根据不同指令执行不同方法:
var commandHandlers = new Dictionary<string, Action>
{
{ "start", () => Console.WriteLine("开始游戏") },
{ "pause", () => Console.WriteLine("暂停游戏") },
{ "quit", () => Console.WriteLine("退出游戏") }
};
string input = "start";
if (commandHandlers.TryGetValue(input, out Action handler))
{
handler();
}
✅ 优雅替代大量
switch-case或if-else,代码更简洁可扩展!
🎯 总结速查表
| 操作 | 方法/语法 |
|---|---|
| 创建字典 | new Dictionary<TKey, TValue>() |
| 添加元素 | .Add(key, value) 或 dict[key] = val |
| 获取值 | dict[key] 或 .TryGetValue(...) |
| 删除元素 | .Remove(key) |
| 判断是否存在 | .ContainsKey(key) |
| 遍历 | foreach(var kvp in dict) |
| 多线程安全 | 使用 ConcurrentDictionary |
| 自定义键类型 | 必须重写 Equals 和 GetHashCode |
📚 学习小贴士
- ✅ 初学阶段先掌握增删改查和遍历,别急着看源码。
- ✅ 实际编码中多用
TryGetValue避免异常。 - ✅ 多线程环境务必使用
ConcurrentDictionary。 - ✅ 自定义键类一定要重写
Equals和GetHashCode!

只是两个人相处,那么喜欢一个人,可能会觉得她所有都好,可是以后在一齐了,就要学会喜欢她的不好。——烽火戏诸侯《剑来》
浙公网安备 33010602011771号