Unity列表 (List) 与链表 (LinkedList) 全面解析:从基础到实战 C#

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 选择策略

  1. 选择List的情况:
  • 需要频繁随机访问
  • 数据量相对固定
  • 尾部操作为主
  • 需要排序/二分查找
  1. 选择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:适用于频繁插入删除、无需随机访问的场景

关键选择因素:

  • 操作类型(访问/插入/删除)
  • 数据规模
  • 内存限制
  • 性能要求

欢迎在评论区分享你的数据结构使用经验!

  • 点赞收藏加关注哦~ 蟹蟹
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与火星的孩子对话

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值