选择原则:需要“键 → 值”映射用
Dictionary<TKey,TValue>;只关心成员是否存在用 HashSet<T>;Job 中并行访问使用 Unity Collections 的原生哈希容器。哈希结构解决什么问题
如果每次都在 List 中按 ID 查找,最坏需要扫描全部元素,复杂度 O(n)。哈希表通过键的哈希值定位桶,平均查询、插入和删除接近 O(1)。
它的代价是额外内存、无稳定遍历顺序,以及对键的相等性实现有要求。
Dictionary:ID 到数据
读取未知键时优先
TryGetValue,避免先 ContainsKey 再索引造成两次查找。HashSet:成员关系与去重
Add 返回 false 表示元素已经存在,常用于防止重复注册、记录已访问节点和集合差集。键必须稳定
把可变对象作为键很危险。若对象进入 Dictionary 后,其参与
Equals 或 GetHashCode 的字段发生变化,之后可能再也找不到该项。值类型键应实现稳定相等性:
浮点位置不适合直接作为逻辑网格键。先量化为整数格坐标,再使用
GridCell。容量与分配
已知大致数量时预设容量:
这能减少扩容和重新哈希。清空后集合通常保留内部容量,适合复用;若长期不再使用,再考虑释放引用。
Unity 序列化
Unity Inspector 默认不直接序列化标准
Dictionary。常见做法:- 序列化
List<Entry>。
- 在
Awake或OnValidate构建运行时 Dictionary。
- 检测重复键并给出明确错误。
不要为了 Inspector 展示而在每次查询时线性扫描序列化 List。
Jobs 与原生哈希容器
Dictionary 和 HashSet 是托管容器,不能直接进入 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。