Unity列表 (List) 与链表 (LinkedList) 全面解析:从基础到实战 C#
提示:内容纯个人编写,欢迎评论点赞,来指正我。
文章目录
1. 数据结构基础
1.1 列表(List)概述
列表是基于动态数组实现的数据结构,在内存中连续存储元素,支持快速随机访问。
优点:
- O(1)时间复杂度随机访问
- 尾部添加/删除元素高效
缺点:
- 中间插入/删除效率低(O(n))
- 动态扩容可能导致性能波动
// 列表内存结构示意图
索引: 0 1 1 3 4
值: [100] [200] [300] [400] [500]
内存地址: 0x100 0x104 0x108 0x10C 0x110
1.2 链表(LinkedList)概述
链表通过节点指针连接元素,在内存中非连续存储,包括单向链表和双向链表。
优点:
- O(1)时间复杂度插入/删除
- 无需连续内存空间
- 动态扩展无性能波动
缺点:
- O(n)时间复杂度随机访问
- 额外内存存储指针
// 双向链表结构示意图
头节点 <-> [节点A|data|next] <-> [节点B|data|next] <-> [节点C|data|next] <-> 尾节点
1.3 二者核心区别
2. 列表(List)在Unity中的使用
2.1 创建与初始化
// 创建整型列表
List<int> intList = new List<int>();
// 初始化并赋值
List<string> stringList = new List<string>{"A", "B", "C"};
// 创建自定义对象列表
public class Player
{
public int id;
public string name;
}
List<Player> playerList = new List<Player>();
2.2 常用操作
// 添加元素
playerList.Add(new Player{id=1, name="Alice"});
// 插入元素
playerList.Insert(0, new Player{id=0, name="Admin"}); // 头部插入
// 访问元素
Player firstPlayer = playerList[0];
// 删除元素
playerList.RemoveAt(0); // 按索引删除
playerList.Remove(player); // 按引用删除
// 遍历
foreach(Player p in playerList)
{
Debug.Log(p.name);
}
// 查找
Player target = playerList.Find(p => p.id == 1);
2.3 适用场景分析
- 频繁随机访问:如根据索引获取道具
- 数据量相对固定:避免频繁扩容
- 尾部操作为主:如日志记录系统
- 需要排序/二分查找:List支持高效排序
// 二分查找示例
List<int> scores = new List<int>{98, 95, 91, 89, 75};
scores.Sort(); // 必须先排序
int index = scores.BinarySearch(91);
3. 链表(LinkedList)在Unity中的使用
3.1 创建与初始化
// 创建整型链表
LinkedList<int> intList = new LinkedList<int>();
// 添加初始元素
intList.AddFirst(10); // 头部添加
intList.AddLast(20); // 尾部添加
// 创建自定义对象链表
LinkedList<Player> playerList = new LinkedList<Player>();
3.2 常用操作
// 添加节点
LinkedListNode<Player> node = playerList.AddLast(new Player{id=1, name="Bob"});
// 在指定节点前后添加
playerList.AddAfter(node, new Player{id=2, name="Charlie"});
// 删除节点
playerList.Remove(node);
// 遍历
foreach(Player p in playerList)
{
Debug.Log(p.name);
}
// 查找(效率较低)
LinkedListNode<Player> targetNode = playerList.Find(playerList.First, p => p.id == 2);
3.3 适用场景分析
- 频繁插入/删除:如游戏中的实时排行榜
- 不需要随机访问:如命令历史记录
- 实现队列/栈:链表天然支持高效的首尾操作
- 内存碎片敏感场景:避免连续内存分配
// 使用链表实现队列
public class LinkedQueue<T>
{
private LinkedList<T> list = new LinkedList<T>();
public void Enqueue(T item) => list.AddLast(item);
public T Dequeue()
{
T item = list.First.Value;
list.RemoveFirst();
return item;
}
}
4. 性能对比与选择建议
4.1 时间复杂度对比
4.2 内存占用分析
- List:仅需存储元素+少量控制数据
- LinkedList:每个元素需额外存储前后节点指针(32位系统每个节点额外8字节,64位系统额外16字节)
4.3 选择策略
- 选择List的情况:
- 需要频繁随机访问
- 数据量相对固定
- 尾部操作为主
- 需要排序/二分查找
- 选择LinkedList的情况:
- 需要频繁在头部/中部插入删除
- 不需要随机访问
- 内存碎片敏感场景
- 实现特殊数据结构(如LRU缓存)
5. 常见问题与解决方案
5.1 列表的容量管理
问题:List动态扩容导致性能波动
List<int> list = new List<int>();
// 添加1000个元素
for(int i=0; i<1000; i++)
{
list.Add(i); // 多次扩容
}
优化:预设容量
List<int> list = new List<int>(1000); // 预设容量
for(int i=0; i<1000; i++)
{
list.Add(i); // 无扩容开销
}
5.2 链表的随机访问优化
问题:链表无法直接索引访问
方案:使用额外字典建立索引
public class IndexedLinkedList<TKey, TValue>
{
private LinkedList<TValue> list = new LinkedList<TValue>();
private Dictionary<TKey, LinkedListNode<TValue>> index = new Dictionary<TKey, LinkedListNode<TValue>>();
public void Add(TKey key, TValue value)
{
var node = list.AddLast(value);
index.Add(key, node);
}
public bool Remove(TKey key)
{
if(index.TryGetValue(key, out var node))
{
list.Remove(node);
index.Remove(key);
return true;
}
return false;
}
}
5.3 循环遍历陷阱
问题:在循环中修改集合
// 错误示例:在foreach循环中删除元素
foreach(var item in list)
{
if(ShouldRemove(item))
list.Remove(item); // 抛出InvalidOperationException
}
正确做法:
// 方法1:使用for循环倒序遍历
for(int i = list.Count - 1; i >= 0; i--)
{
if(ShouldRemove(list[i]))
list.RemoveAt(i);
}
// 方法2:使用临时列表
List<Item> toRemove = new List<Item>();
foreach(var item in list)
{
if(ShouldRemove(item))
toRemove.Add(item);
}
foreach(var item in toRemove)
{
list.Remove(item);
}
6. 实战案例:对象池实现
6.1 基于列表的对象池
public class ListObjectPool : MonoBehaviour
{
public GameObject prefab;
public int initialSize = 10;
private List<GameObject> pool = new List<GameObject>();
void Start()
{
for(int i=0; i<initialSize; i++)
{
CreateNewObject();
}
}
GameObject CreateNewObject()
{
GameObject obj = Instantiate(prefab);
obj.SetActive(false);
pool.Add(obj);
return obj;
}
public GameObject GetObject()
{
// 查找可用对象
foreach(GameObject obj in pool)
{
if(!obj.activeInHierarchy)
{
obj.SetActive(true);
return obj;
}
}
// 无可用对象则创建新对象
GameObject newObj = CreateNewObject();
newObj.SetActive(true);
return newObj;
}
}
6.2 基于链表的对象池
public class LinkedListObjectPool : MonoBehaviour
{
public GameObject prefab;
public int initialSize = 10;
private LinkedList<GameObject> freeList = new LinkedList<GameObject>();
private List<GameObject> allObjects = new List<GameObject>();
void Start()
{
for(int i=0; i<initialSize; i++)
{
CreateNewObject();
}
}
void CreateNewObject()
{
GameObject obj = Instantiate(prefab);
obj.SetActive(false);
freeList.AddLast(obj);
allObjects.Add(obj);
}
public GameObject GetObject()
{
if(freeList.Count == 0)
{
CreateNewObject();
}
GameObject obj = freeList.First.Value;
freeList.RemoveFirst();
obj.SetActive(true);
return obj;
}
public void ReturnObject(GameObject obj)
{
obj.SetActive(false);
freeList.AddLast(obj);
}
}
性能对比:
- 链表实现:获取对象O(1),归还对象O(1)
- 列表实现:最坏情况需要遍历整个列表
7. 实战案例:回合制游戏行动顺序管理
7.1 需求分析
在回合制游戏中:
- 角色按角色速度决定行动顺序
- 可能随时添加/删除角色
- 需要高效插入新角色
7.2 基于链表的实现
public class TurnManager
{
private LinkedList<Character> turnOrder = new LinkedList<Character>();
// 添加角色到适当位置(按速度排序)
public void AddCharacter(Character character)
{
if(turnOrder.Count == 0)
{
turnOrder.AddFirst(character);
return;
}
var current = turnOrder.First;
while(current != null)
{
if(character.speed > current.Value.speed)
{
turnOrder.AddBefore(current, character);
return;
}
current = current.Next;
}
turnOrder.AddLast(character);
}
// 移除角色
public void RemoveCharacter(Character character)
{
var node = FindNode(character);
if(node != null)
{
turnOrder.Remove(node);
}
}
// 获取下一个行动角色
public Character GetNext()
{
if(turnOrder.Count == 0) return null;
Character next = turnOrder.First.Value;
turnOrder.RemoveFirst();
return next;
}
// 将当前角色放回队列尾部(速度减半)
public void ReturnCharacter(Character character)
{
character.speed /= 2;
AddCharacter(character);
}
private LinkedListNode<Character> FindNode(Character character)
{
var current = turnOrder.First;
while(current != null)
{
if(current.Value == character)
return current;
current = current.Next;
}
return null;
}
}
7.3 性能优化技巧
- 缓存节点引用:为每个角色存储其链表节点
- 使用跳表结构:对于大量角色可考虑使用更高效数据结构
- 批量更新:避免频繁单次操作
8. 实战案例:敌人管理系统
8.1 基于列表的实现
public class EnemyManager : MonoBehaviour
{
private List<Enemy> enemies = new List<Enemy>();
public void AddEnemy(Enemy enemy)
{
enemies.Add(enemy);
}
public void RemoveEnemy(Enemy enemy)
{
enemies.Remove(enemy);
}
public Enemy GetNearestEnemy(Vector3 position)
{
Enemy nearest = null;
float minDistance = float.MaxValue;
foreach(Enemy enemy in enemies)
{
float dist = Vector3.Distance(position, enemy.Position);
if(dist < minDistance)
{
minDistance = dist;
nearest = enemy;
}
}
return nearest;
}
void Update()
{
// 倒序遍历避免索引问题
for(int i = enemies.Count - 1; i >= 0; i--)
{
if(enemies[i].Health <= 0)
enemies.RemoveAt(i);
}
}
}
8.2 基于链表的实现
public class EnemyManager : MonoBehaviour
{
private LinkedList<Enemy> enemies = new LinkedList<Enemy>();
private Dictionary<Enemy, LinkedListNode<Enemy>> nodeMap = new Dictionary<Enemy, LinkedListNode<Enemy>>();
public void AddEnemy(Enemy enemy)
{
LinkedListNode<Enemy> node = enemies.AddLast(enemy);
nodeMap.Add(enemy, node);
}
public void RemoveEnemy(Enemy enemy)
{
if(nodeMap.TryGetValue(enemy, out var node))
{
enemies.Remove(node);
nodeMap.Remove(enemy);
}
}
void Update()
{
LinkedListNode<Enemy> currentNode = enemies.First;
while(currentNode != null)
{
LinkedListNode<Enemy> next = currentNode.Next;
if(currentNode.Value.Health <= 0)
RemoveEnemy(currentNode.Value);
currentNode = next;
}
}
}
性能对比:
- 列表实现:删除死亡敌人O(n)
- 链表实现:删除死亡敌人O(1)
9. 实战案例:命令历史记录
9.1 需求分析
实现游戏命令系统:
- 记录玩家所有操作命令
- 支持撤销(Undo)/重做(Redo)
- 限制最大历史记录数
9.2 链表实现方案
public class CommandHistory
{
private LinkedList<ICommand> history = new LinkedList<ICommand>();
private LinkedListNode<ICommand> current;
private int maxHistory = 50;
public void ExecuteCommand(ICommand command)
{
command.Execute();
// 清除当前节点后的历史
if(current != null)
{
var {
var node = current.Next;
while(node != null)
{
var next = node.Next;
history.Remove(node);
node = next;
}
}
// 添加新命令
history.AddLast(command);
current = history.Last;
// 限制历史记录数量
if(history.Count > maxHistory)
{
history.RemoveFirst();
}
}
public bool CanUndo => current != null;
public void Undo()
{
if(CanUndo)
{
current.Value.Undo();
current = current.Previous;
// 添加回退点标记
history.AddAfter(current, new MarkerCommand("Undo Point"));
current = current.Next;
}
public bool CanRedo => current != null && current.Next != null;
public void Redo()
{
if(CanRedo)
{
current = current.Next;
current.Value.Execute();
}
}
}
public interface ICommand
{
void Execute();
void Undo();
}
public class MarkerCommand : ICommand
{
private string name;
public MarkerCommand(string name) => this.name = name;
public void Execute() {}
public void Undo() {}
}
实现特点:
- 使用链表高效插入/删除命令
- 支持任意位置撤销/重做
- 使用标记点标识撤销点
- 限制历史记录大小
10. 总结
本文详细对比了Unity中List和LinkedList两种数据结构:
- List:适用于随机访问频繁、尾部操作为主的场景
- LinkedList:适用于频繁插入删除、无需随机访问的场景
关键选择因素:
- 操作类型(访问/插入/删除)
- 数据规模
- 内存限制
- 性能要求
欢迎在评论区分享你的数据结构使用经验!
- 点赞收藏加关注哦~ 蟹蟹