这不是容器 API 清单:选择数据结构时,先看最频繁的操作、数据规模、顺序要求、生命周期和线程环境,再决定用什么。
数据结构是什么先按需求选择决策流程时间复杂度速查Unity 特有的判断维度Inspector 与序列化UnityEngine.ObjectGCJobs 与 Burst确定性学习顺序系列导航实践原则相关阅读
数据结构是什么
数据结构决定数据如何组织,以及查询、插入、删除和遍历要付出什么成本。Unity 开发除了关注时间复杂度,还必须考虑:
- 连续内存与 CPU 缓存
- 托管分配和 GC
- Inspector 序列化
- GameObject / UnityEngine.Object 生命周期
- Jobs、Burst 和线程安全
- 是否需要稳定、确定的遍历顺序
Vector3、Color、Rect 是常用值类型,但不是本系列所说的集合数据结构。先按需求选择
核心需求 | 优先选择 | 典型 Unity 场景 |
固定长度、按索引访问 | Array | 技能槽、出生点 |
动态连续列表 | List | 敌人、任务、UI 项 |
按 ID 找对象 | Dictionary | 配置表、实体注册表 |
去重或成员判断 | HashSet | 已访问节点、触发区域 |
后进先出 | Stack | UI 返回、撤销、DFS |
先进先出 | Queue | BFS、消息、分帧任务 |
按优先级取出 | Binary Heap | A*、AI 调度 |
已知节点处频繁插删 | LinkedList | LRU、特殊调度链 |
严格父子层级 | Tree | Transform、UI、行为树 |
任意节点关系 | Graph | 寻路、任务依赖 |
邻域与范围查询 | Spatial Index | 大量单位、可见性 |
Jobs / Burst | Native Container | 批量数值计算 |
决策流程
时间复杂度速查
结构 | 索引/查询 | 尾部添加 | 中间删除 |
Array | 索引 O(1) | 不可变长 | O(n) |
List | 索引 O(1)、查值 O(n) | 均摊 O(1) | O(n) |
Dictionary / HashSet | 平均 O(1) | 平均 O(1) | 平均 O(1) |
LinkedList | O(n) | 有尾节点时 O(1) | 已知节点时 O(1) |
Binary Heap | 堆顶 O(1) | O(log n) | 弹出堆顶 O(log n) |
Tree / Graph | 取决于索引与算法 | 取决于表示 | 取决于表示 |
大 O 描述增长趋势,不等于实际耗时。数据只有十几个时,连续 List 的线性扫描可能比哈希表更快;链表虽然插入复杂度低,却可能因节点分配和缓存局部性表现更差。
Unity 特有的判断维度
Inspector 与序列化
Array 和 List 能直接序列化。标准 Dictionary 默认不能直接显示,通常要用序列化 Entry 列表,在加载阶段构建运行时索引。
UnityEngine.Object
被 Destroy 的对象可能表现为“伪 null”。长期集合要在对象销毁或禁用时及时注销,避免残留引用和无效遍历。
GC
预设 List/Dictionary 容量、复用集合、避免热路径 LINQ。不要为了“零 GC”过早迁移 Native Container,原生内存和同步也有成本。
Jobs 与 Burst
托管集合不能直接进入 Burst Job。Native Container 要显式管理 Allocator、JobHandle 和 Dispose。
确定性
Dictionary、HashSet 和并行 Writer 的遍历/插入顺序不应作为确定性游戏逻辑依据。需要稳定结果时显式排序或使用稳定索引。
学习顺序
- Array 与 List:掌握连续内存、容量和删除成本。
- Dictionary 与 HashSet:掌握哈希键、去重和索引。
- Stack、Queue、Heap:掌握处理顺序。
- LinkedList:理解引用结构与缓存代价。
- Tree 与 Graph:表达层级和关系。
- Spatial Index:解决大量对象邻域查询。
- Native Containers:进入 Jobs/Burst 与数据导向。
系列导航
实践原则
- 先写清主要操作,再选容器。
- 先用简单结构实现正确版本,再用 Profiler 找热点。
- 预估容量并复用高频集合。
- 不依赖未承诺的遍历顺序。
- 数据结构负责组织数据,算法决定如何使用它。