1. 程式人生 > >Unity UGUI原始碼——容器IndexedSet

Unity UGUI原始碼——容器IndexedSet

本文由本人簡書搬遷至此,並做小幅修改。

List 和 Dictionary,是最常用的資料結構之二。

先來看看List 和 Dictionary的優缺點:
1.遍歷,List可以 for 可以 foreach 還可以.ForEach(),而 Dictionary只能foreach (Unity某些版本使用foreach會由於拆裝箱產生GC)。List的遍歷順序就是元素的新增順序,Dictionary是Hash方式儲存的,所以遍歷時無法保證順序。
2.List是支援排序的(key為固定的整數索引)。可以直接調Sort()、Reverse()等方法, Dictionary不支援(key型別不確定)。
3.List查詢慢,需要遍歷,時間複雜度是 O(n), Dictionary查詢快,使用雜湊查詢方式,時間複雜度為 O(1)。
4.List 移除效率低,內部由陣列實現並非連結串列。 Remove(item)需要先遍歷查詢到對應的index,RemoveAt(index)方法需要從在移除index處的元素後,後邊的元素依次向前補位。在List長度較長甚至同時需要多次移除時,對效能影響較大。

msdn List 原始碼地址
msdn Dictionary 原始碼地址

--------------------NRatel割--------------------

今天看UGUI原始碼時發現,Unity UnityEngine.UI.Collections 名稱空間下實現了一個IndexedSet類,比較巧妙地吸取了List 和 Dictionary各自的優點。當然也不是完美的)。

程式碼比較簡單。這裡我對它的註釋做一下解釋。

這是這樣的一個容器:
1.items 都是唯一的 (item必須唯一,因為要用item作為字典的key, 這樣帶來的好處是查詢變成了快速的hash查詢方式)。
2.快速隨機移除 (移除時Remove不需要再先做線性查找了。RemoveAt將要移除的元素與尾部元素換位,然後移除尾部,避免了後邊元素依次向前補位。這塊其實也打亂了原來的排序,不過問題不大,如果想要恢復排序。可以在所有移除進行完之後執行一次Sort)
3.Fast unique inclusion to the end (暫時沒明白這句話的意思, 知道的可以留言告訴我)
4.順序訪問(支援通過index查詢,支援有序,支援靈活的遍歷方式)

缺點:
1佔用更多的記憶體。 (沒辦法,本來只用存一份,現在要列表中一份,字典中一份)
2排序是非永久的。(移除操作為了提升效率,會打亂排序)
3不易序列化. (因為是組合的資料結構)

可以在符合上述條件的、恰當的情況下使用它。
由於它是 internal class,不能直接調它的實現,可以拷貝一份出來再用。

既然寫到這了,不妨再捋一捋C#的資料結構。

最後,貼上 IndexedSet 的原始碼,(純貼上自 Unity 2017.3.1f1)

using System;
using System.Collections;
using System.Collections.Generic;

namespace UnityEngine.UI.Collections
{
    internal class IndexedSet<T> : IList<T>
    {
        //This is a container that gives:
        //  - Unique items
        //  - Fast random removal
        //  - Fast unique inclusion to the end
        //  - Sequential access
        //Downsides:
        //  - Uses more memory
        //  - Ordering is not persistent
        //  - Not Serialization Friendly.

        //We use a Dictionary to speed up list lookup, this makes it cheaper to guarantee no duplicates (set)
        //When removing we move the last item to the removed item position, this way we only need to update the index cache of a single item. (fast removal)
        //Order of the elements is not guaranteed. A removal will change the order of the items.

        readonly List<T> m_List = new List<T>();
        Dictionary<T, int> m_Dictionary = new Dictionary<T, int>();

        public void Add(T item)
        {
            if (m_Dictionary.ContainsKey(item))
                return;

            m_List.Add(item);
            m_Dictionary.Add(item, m_List.Count - 1);
        }

        public bool Remove(T item)
        {
            int index = -1;
            if (!m_Dictionary.TryGetValue(item, out index))
                return false;

            RemoveAt(index);
            return true;
        }

        public IEnumerator<T> GetEnumerator()
        {
            throw new System.NotImplementedException();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public void Clear()
        {
            m_List.Clear();
            m_Dictionary.Clear();
        }

        public bool Contains(T item)
        {
            return m_Dictionary.ContainsKey(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            m_List.CopyTo(array, arrayIndex);
        }

        public int Count { get { return m_List.Count; } }
        public bool IsReadOnly { get { return false; } }
        public int IndexOf(T item)
        {
            int index = -1;
            m_Dictionary.TryGetValue(item, out index);
            return index;
        }

        public void Insert(int index, T item)
        {
            //We could support this, but the semantics would be weird. Order is not guaranteed..
            throw new NotSupportedException("Random Insertion is semantically invalid, since this structure does not guarantee ordering.");
        }

        public void RemoveAt(int index)
        {
            T item = m_List[index];
            m_Dictionary.Remove(item);
            if (index == m_List.Count - 1)
                m_List.RemoveAt(index);
            else
            {
                int replaceItemIndex = m_List.Count - 1;
                T replaceItem = m_List[replaceItemIndex];
                m_List[index] = replaceItem;
                m_Dictionary[replaceItem] = index;
                m_List.RemoveAt(replaceItemIndex);
            }
        }

        public T this[int index]
        {
            get { return m_List[index]; }
            set
            {
                T item = m_List[index];
                m_Dictionary.Remove(item);
                m_List[index] = value;
                m_Dictionary.Add(item, index);
            }
        }

        public void RemoveAll(Predicate<T> match)
        {
            //I guess this could be optmized by instead of removing the items from the list immediatly,
            //We move them to the end, and then remove all in one go.
            //But I don't think this is going to be the bottleneck, so leaving as is for now.
            int i = 0;
            while (i < m_List.Count)
            {
                T item = m_List[i];
                if (match(item))
                    Remove(item);
                else
                    i++;
            }
        }

        //Sorts the internal list, this makes the exposed index accessor sorted as well.
        //But note that any insertion or deletion, can unorder the collection again.
        public void Sort(Comparison<T> sortLayoutFunction)
        {
            //There might be better ways to sort and keep the dictionary index up to date.
            m_List.Sort(sortLayoutFunction);
            //Rebuild the dictionary index.
            for (int i = 0; i < m_List.Count; ++i)
            {
                T item = m_List[i];
                m_Dictionary[item] = i;
            }
        }
    }
}

--------------------NRatel割--------------------
NRatel
轉載請說明出處,謝謝