Unity 补完计划(四):UGUI-2:IndexedSet
IndexedSet
UGUI 中定义了一种特殊的容器,被称为 IndexedSet
。该容器结合了 List
与 Dictionary
使得它具有以下的优点:
-
保证容器中元素的唯一性(类似于
Set
) -
快速的随机元素删除(使用了
swap
的技巧) -
连续访问(由
List
保证的缓存友好特性)
但同时,这种容器也有一些缺点:
-
使用更多的内存(同时维护了一个
List
与一个Dictionary
) -
顺序并不能一直保持(
Swap
技巧的弊端) -
序列化不友好(同时维护两种数据结构)
下面就来看一下该类的实现。
首先,该类被标记为 internal
。这说明该容器仅有 UGUI 程序集内部能够使用;其次,该类是一个泛型类并继承了 IList<T>
。
此外可以看出,类中同时维护了一个 List
与一个 Dictionary
。
1 | internal class IndexedSet<T> : IList<T> |
IList
是 .NET
中的一个接口,继承自 ICollection
和 IEnumerable
。它定义了一些用于操作集合的标准方法和属性。
1 | public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable |
回到 IndexedSet
类中,其中的 Add
及 AddUnique
方法分别用于向容器中添加元素。Add
方法直接添加元素,而 AddUnique
方法则会先检查元素是否已经存在于容器中,若存在则返回 false
,否则添加元素并返回 true
。
可以看出,Dictionary
中的键为插入到 List
中的元素,而值为该元素在 List
中的索引。因而可以通过 Dictionary
快速查找元素在 List
中的索引。
在 Add
与 AddUnique
方法中,会同时向 List
与 Dictionary
中添加元素。List
用于保证元素的顺序,而 Dictionary
用于保证元素的唯一性。
1 | public void Add(T item) |
接下来是 Remove
相关方法的实现,这里使用了 swap
的技巧。当需要删除一个元素时,将该元素与最后一个元素交换,然后删除最后一个元素。这样做的好处是只需要更新一个元素的索引,而不用移动所有元素。但同时,这也会打乱元素的顺序。
1 | public bool Remove(T item) |
为了恢复元素的顺序,IndexedSet
类中还提供了 Sort
方法。该方法会对 List
中的元素进行排序,并重新构建 Dictionary
中的索引。
1 | //Sorts the internal list, this makes the exposed index accessor sorted as well. |
此外,IndexedSet
类还提供了一些其他方法,如 Clear
、Contains
、CopyTo
、IndexOf
等,这里就不一一分析了。
但有两个地方比较有意思:
1 | public IEnumerator<T> GetEnumerator() |
这两个方法分别实现了 IEnumerable<T>
与 IEnumerable
接口。但是它们都抛出了一个 NotImplementedException
异常。这是因为 IndexedSet
类并不希望外部直接通过 foreach
来遍历元素,而是通过 List
的索引来访问元素。
1 | public void Insert(int index, T item) |
Insert
方法也抛出了一个异常。虽然 List
与 Dictionary
本身都支持插入操作,但是 IndexedSet
类并不希望外部直接插入元素。因为这样会打破元素的顺序。
总结
IndexedSet
类是 UGUI 中的一个特殨容器,它结合了 List
与 Dictionary
的优点,同时也有一些缺点。这种容器适用于需要保证元素唯一性、快速删除元素、连续访问元素的场景。
IndexedSet
内部维护了一个 List
与一个 Dictionary
,List
用于保证元素的顺序,而 Dictionary
用于保证元素的唯一性。当需要删除元素时,IndexedSet
会使用 swap
的技巧,将需要删除的元素与最后一个元素交换,然后删除最后一个元素。