🔎
选择原则:需要“键 → 值”映射用 Dictionary<TKey,TValue>;只关心成员是否存在用 HashSet<T>;Job 中并行访问使用 Unity Collections 的原生哈希容器。

哈希结构解决什么问题

如果每次都在 List 中按 ID 查找,最坏需要扫描全部元素,复杂度 O(n)。哈希表通过键的哈希值定位桶,平均查询、插入和删除接近 O(1)。
它的代价是额外内存、无稳定遍历顺序,以及对键的相等性实现有要求。

Dictionary:ID 到数据

读取未知键时优先 TryGetValue,避免先 ContainsKey 再索引造成两次查找。

HashSet:成员关系与去重

Add 返回 false 表示元素已经存在,常用于防止重复注册、记录已访问节点和集合差集。

键必须稳定

把可变对象作为键很危险。若对象进入 Dictionary 后,其参与 EqualsGetHashCode 的字段发生变化,之后可能再也找不到该项。
值类型键应实现稳定相等性:
浮点位置不适合直接作为逻辑网格键。先量化为整数格坐标,再使用 GridCell

容量与分配

已知大致数量时预设容量:
这能减少扩容和重新哈希。清空后集合通常保留内部容量,适合复用;若长期不再使用,再考虑释放引用。

Unity 序列化

Unity Inspector 默认不直接序列化标准 Dictionary。常见做法:
  1. 序列化 List<Entry>
  1. AwakeOnValidate 构建运行时 Dictionary。
  1. 检测重复键并给出明确错误。
不要为了 Inspector 展示而在每次查询时线性扫描序列化 List。

Jobs 与原生哈希容器

DictionaryHashSet 是托管容器,不能直接进入 Burst Job。Unity Collections 提供:
  • NativeHashMap<TKey,TValue>
  • NativeParallelHashMap<TKey,TValue>
  • NativeHashSet<T>
并行写入时使用容器提供的 ParallelWriter,预先设置足够容量,并通过 JobHandle 管理依赖和释放。

复杂度与边界

操作
平均
注意
查询
O(1)
哈希冲突严重时会退化
插入
O(1)
扩容时会重新分配
删除
O(1)
不要依赖遍历顺序
全部遍历
O(n)
缓存局部性通常弱于 List
少量数据或需要稳定顺序时,List 可能更简单、更快。哈希结构不是“永远比 List 快”,它优化的是按键查询。

常见坑

  • 依赖 Dictionary 的遍历顺序做游戏逻辑或存档。
  • 使用会变化的名称、坐标或状态对象作为键。
  • 在热循环中重复创建临时 Dictionary。
  • dict[key] 读取不确定存在的键。
  • 忘记自定义键的相等性和哈希必须一致。
  • 多线程同时写普通 Dictionary。

系列导航

  1. 常用数据结构:Unity/C# 选择指南
  1. Array / List / NativeArray:连续内存容器
  1. Dictionary / HashSet / NativeHashMap:哈希容器
  1. Stack / Queue / Binary Heap:顺序与优先级
  1. 链表:Unity 中何时使用,何时避开
  1. Tree:层级、搜索与决策
  1. Graph:寻路、依赖与关系网络
  1. 空间索引:Grid / Spatial Hash / Quadtree / Octree
  1. Unity Native Containers:Jobs / Burst 下的数据结构

参考资料