版權(quán)聲明:本文為博主原創(chuàng)文章,歡迎轉(zhuǎn)載。請(qǐng)保留博主鏈接:http://blog.csdn.net/andrewfan
上篇文章《【Unity優(yōu)化】Unity中究竟能不能使用foreach?》發(fā)表之后,曾經(jīng)有網(wǎng)友說(shuō),在他的不同的Unity版本上,發(fā)現(xiàn)了泛型List無(wú)論使用foreach還是GetEnumerator均會(huì)產(chǎn)生GC的情況,這就有點(diǎn)尷尬了。由于它本身就是Mono編譯器和相應(yīng).net庫(kù)才能決定的原因,這就使得在使用系統(tǒng)提供的List時(shí),又能最終擺脫GC的糾纏變得很困難。于是抓耳撓腮,翻出差不多六七年為Java代碼寫(xiě)的動(dòng)態(tài)數(shù)組,然后大肆修改一番。最終好像終于逃離GC的魔咒。
先奉上代碼:
自定義的List
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AndrewBox.Math
{
/// <summary>
/// 動(dòng)態(tài)數(shù)組
/// @author AndrewFan
/// </summary>
/// <typeparam name="T">任意類型</typeparam>
public class AB_List<T> :IEnumerable<T>
{
protected int m_capacity=10; // 容量
protected T[] m_items;// 內(nèi)部數(shù)組
protected int m_length;// 存放的單元個(gè)數(shù)
protected int m_mayIdleID;// 可能空閑的單元下標(biāo)
protected IEnumerator<T>[] m_enumerators; //枚舉器組
protected bool[] m_enumStates;//枚舉器組當(dāng)前占用狀態(tài)
public AB_List()
{
init(5);
}
public AB_List(int capacity,int enumCount=5)
{
init(enumCount);
}
protected void init(int enumCount)
{
m_capacity = m_capacity<10?10:m_capacity;
enumCount = enumCount < 5 ? 5 : enumCount;
m_items = new T[m_capacity];
if (m_enumerators == null)
{
m_enumerators = new IEnumerator<T>[enumCount];
m_enumStates = new bool[enumCount];
for (int i = 0; i < m_enumerators.Length; i++)
{
m_enumerators[i] = new ABEnumerator<T>(this,i);
}
}
}
/// <summary>
/// 增加單元
/// </summary>
/// <param name="element">添加的單元</param>
public virtual void Add(T element)
{
increaseCapacity();
// 賦值
m_items[m_length] = element;
m_length++;
}
/// <summary>
/// 插入單元
/// </summary>
/// <param name="index">插入位置</param>
/// <param name="element">單元</param>
/// <returns>操作是否成功</returns>
public virtual bool Insert(int index, T element)
{
if (index < 0)
{
return false;
}
if (index >= m_length)
{
Add(element);
return true;
}
increaseCapacity();
// 向后拷貝
// for(int i=length;i>index;i--)
// {
// datas[i]=datas[i-1];
// }
System.Array.Copy(m_items, index, m_items, index + 1, m_length - index);
m_items[index] = element;
m_length++;
return true;
}
public virtual T this[int index]
{
get
{
//取位于某個(gè)位置的單元
if (index < 0 || index >= m_length)
{
throw new InvalidOperationException();
}
return m_items[index];
}
set
{
//設(shè)置位于某個(gè)位置的單元
if (index < 0 || index >= m_length)
{
throw new InvalidOperationException();
}
m_items[index] = value;
}
}
/// <summary>
/// 增長(zhǎng)容量
/// </summary>
protected void increaseCapacity()
{
if (m_length >= m_capacity)
{
int newCapacity = m_capacity;
if(newCapacity == 0)
{
newCapacity++;
}
newCapacity *= 2;
T[] datasNew = new T[newCapacity];
System.Array.Copy(m_items, 0, datasNew, 0, m_length);
m_items = datasNew;
m_capacity = newCapacity;
}
}
/// <summary>
/// 清空單元數(shù)組
/// </summary>
public virtual void Clear()
{
for (int i = 0; i < m_length; i++)
{
m_items[i] = default(T);
}
m_length = 0;
}
/// <summary>
/// 是否包含某個(gè)單元
/// </summary>
/// <param name="element">單元</param>
/// <returns>是否包含</returns>
public bool Contains(T element)
{
for (int i = 0; i < m_length; i++)
{
if (m_items[i].Equals(element))
{
return true;
}
}
return false;
}
/// <summary>
/// 獲取指定單元在當(dāng)前列表中的位置,從前向后查找
/// </summary>
/// <param name="element">單元</param>
/// <returns>位置</returns>
public int IndexOf(T element)
{
for (int i = 0; i < m_length; i++)
{
if (m_items[i].Equals(element))
{
return i;
}
}
return -1;
}
/// <summary>
/// 獲取指定單元在當(dāng)前列表中的位置,從后先前查找
/// </summary>
/// <param name="element">單元</param>
/// <returns>位置</returns>
public int LastIndexOf(T element)
{
for (int i = m_length-1; i >=0; i--)
{
if (m_items[i].Equals(element))
{
return i;
}
}
return -1;
}
/// <summary>
/// 獲得長(zhǎng)度
/// </summary>
public virtual int Count
{
get
{
return m_length;
}
}
/// <summary>
/// 移除指定位置的單元,如果單元?dú)w屬權(quán)屬于當(dāng)前列表,則會(huì)將其卸載
/// </summary>
/// <param name="index">位置索引</param>
/// <returns>移除掉的單元</returns>
public virtual void RemoveAt(int index)
{
if (index < 0 || index >= m_length)
{
return;
}
for (int i = index; i <= m_length - 2; i++)
{
m_items[i] = m_items[i + 1];
}
m_length--;
}
/// <summary>
/// 移除指定尾部單元
/// </summary>
/// <returns>移除掉的單元</returns>
public virtual T RemoveEnd()
{
if (m_length <= 0)
{
return default(T);
}
T temp = m_items[m_length - 1];
m_items[m_length - 1] = default(T);
m_length--;
return temp;
}
/// <summary>
/// 從指定位置開(kāi)始(包括當(dāng)前),移除后續(xù)單元,如果單元?dú)w屬權(quán)屬于當(dāng)前列表,則會(huì)將其卸載
/// </summary>
/// <param name="index">要移除的位置</param>
/// <param name="innerMove">是否是內(nèi)部移動(dòng)</param>
/// <returns>被移除的個(gè)數(shù),如果index越界,則返回-1</returns>
public virtual int RemoveAllFrom(int index)
{
if (index < 0 || index >= m_length)
{
return -1;
}
int removedNum = 0;
for (int i = m_length - 1; i >= index; i--)
{
m_items[i] = default(T);
m_length--;
removedNum++;
}
return removedNum;
}
/// <summary>
/// 移除指定單元,如果單元?dú)w屬權(quán)屬于當(dāng)前列表,則會(huì)將其卸載
/// </summary>
/// <param name="element">單元</param>
/// <returns>是否操作成功</returns>
public virtual bool Remove(T element)
{
int index = IndexOf(element);
if (index < 0)
{
return false;
}
RemoveAt(index);
return true;
}
/// <summary>
/// 獲取所有數(shù)據(jù),注意這里的數(shù)據(jù)可能包含了很多冗余空數(shù)據(jù),長(zhǎng)度>=當(dāng)前數(shù)組長(zhǎng)度。
/// </summary>
/// <returns>所有數(shù)據(jù)數(shù)組</returns>
public T[] GetAllItems()
{
return m_items;
}
/// <summary>
/// 轉(zhuǎn)換成定長(zhǎng)數(shù)組,伴隨著內(nèi)容拷貝。
/// 如果是值類型數(shù)組,將與本動(dòng)態(tài)數(shù)組失去關(guān)聯(lián);
/// 如果是引用類型數(shù)組,將與本動(dòng)態(tài)數(shù)組保存相同的引用。
/// </summary>
/// <returns>數(shù)組</returns>
public virtual Array ToArray()
{
T[] array = new T[m_length];
for (int i = 0; i < m_length; i++)
{
array[i] = m_items[i];
}
return array;
}
/// <summary>
/// 顯示此數(shù)組,每個(gè)單元之間以逗號(hào)分隔
/// </summary>
public void Show()
{
string text = "";
for (int i = 0; i < m_length; i++)
{
T obj = m_items[i];
text += (obj.ToString() + ",");
}
Debug.Log(text);
}
/// <summary>
/// 顯示此數(shù)組,每個(gè)單元一行
/// </summary>
public void ShowByLines()
{
string text = "";
for (int i = 0; i < m_length; i++)
{
T obj = m_items[i];
text += (obj.ToString());
}
Debug.Log(text);
}
public IEnumerator<T> GetEnumerator()
{
//搜索可用的枚舉器
int idleEnumID = -1;
for (int i = 0; i < m_enumStates.Length; i++)
{
int tryID=i+m_mayIdleID;
if (!m_enumStates[tryID])
{
idleEnumID = tryID;
break;
}
}
if (idleEnumID < 0)
{
Debug.LogError("use too much enumerators");
}
//標(biāo)記為已經(jīng)使用狀態(tài)
m_enumStates[idleEnumID] = true;
m_enumerators[idleEnumID].Reset();
//向前遷移空閑坐標(biāo)
m_mayIdleID = (m_mayIdleID + 1) % m_enumStates.Length;
return m_enumerators[idleEnumID];
}
IEnumerator IEnumerable.GetEnumerator()
{
return null;
}
struct ABEnumerator<T> : IDisposable, IEnumerator<T>
{
private AB_List<T> m_list;
private int m_idNext;
private T m_current;
private int m_id;
public object Current
{
get
{
if (this.m_idNext <= 0)
{
throw new InvalidOperationException();
}
return this.m_current;
}
}
T IEnumerator<T>.Current
{
get
{
return this.m_current;
}
}
internal ABEnumerator(AB_List<T> list,int id)
{
this.m_list = list;
this.m_idNext = 0;
this.m_id=id;
m_current = default(T);
}
void IEnumerator.Reset()
{
this.m_idNext = 0;
}
public void Dispose()
{
//this.m_list = null;
//清除使用標(biāo)記
m_list.m_enumStates[m_id] = false;
m_list.m_mayIdleID = m_id;
}
public bool MoveNext()
{
if (this.m_list == null)
{
throw new ObjectDisposedException(base.GetType().FullName);
}
if (this.m_idNext < 0)
{
return false;
}
if (this.m_idNext < this.m_list.Count)
{
this.m_current = this.m_list.m_items[this.m_idNext++];
return true;
}
this.m_idNext = -1;
return false;
}
}
}
}
下面是修改后的ForeachTest 類
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using AndrewBox.Math;
public class ForeachTest : MonoBehaviour {
int[] m_intArray;
List<int> m_intList;
ArrayList m_arryList;
AB_List<int> m_intABList;
public void Start ()
{
m_intArray = new int[2];
m_intList = new List<int>();
m_arryList = new ArrayList();
m_intABList = new AB_List<int>();
for (int i = 0; i < m_intArray.Length; i++)
{
m_intArray[i] = i;
m_intList.Add(i);
m_arryList.Add(i);
m_intABList.Add(i);
}
}
void Update ()
{
testABListEnumLevel();
//testABListGetEmulator();
//testABListForeach();
}
void testIntListForeach()
{
for (int i = 0; i < 1000; i++)
{
foreach (var iNum in m_intList)
{
}
}
}
void testIntListGetEmulator()
{
for (int i = 0; i < 1000; i++)
{
var iNum = m_intList.GetEnumerator();
while (iNum.MoveNext())
{
}
}
}
void testIntArrayForeach()
{
for (int i = 0; i < 1000; i++)
{
foreach (var iNum in m_intArray)
{
}
}
}
void testIntArrayGetEmulator()
{
for (int i = 0; i < 1000; i++)
{
var iNum = m_intArray.GetEnumerator();
while (iNum.MoveNext())
{
}
}
}
void testArrayListForeach()
{
for (int i = 0; i < 1000; i++)
{
foreach (var iNum in m_arryList)
{
}
}
}
void testArrayListGetEmulator()
{
for (int i = 0; i < 1000; i++)
{
var iNum = m_arryList.GetEnumerator();
while (iNum.MoveNext())
{
}
}
}
void testABListForeach()
{
for (int i = 0; i < 1000; i++)
{
foreach (var iNum in m_intABList)
{
}
}
}
void testABListGetEmulator()
{
for (int i = 0; i < 1000; i++)
{
using (var iNum = m_intABList.GetEnumerator())
{
while (iNum.MoveNext())
{
var t = iNum.Current;
}
}
}
}
void testABListEnumLevel()
{
foreach (var iNum1 in m_intABList)
{
foreach (var iNum2 in m_intABList)
{
foreach (var iNum3 in m_intABList)
{
foreach (var iNum4 in m_intABList)
{
//foreach (var iNum5 in m_intABList)
//{
//}
}
}
}
}
}
}
Foreach調(diào)用解析
關(guān)鍵之處作個(gè)解釋:
首先理清楚IEnumerable、IEnumerator之間的關(guān)系。
IEnumerable是指那種可以被枚舉的列表類型,如果我們自己自定義一個(gè)List,希望它能結(jié)合foreach使用的話,必須實(shí)現(xiàn)這個(gè)接口。
IEnumerator是一個(gè)枚舉器。
系統(tǒng)庫(kù)里的IEnumerable接口是這樣:
using System.Runtime.InteropServices;
namespace System.Collections
{
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
}
在我的ABList類中實(shí)現(xiàn)接口的函數(shù)是下面這樣:
IEnumerator IEnumerable.GetEnumerator()
{
if (m_enumerator == null)
{
m_enumerator = new ABEnumerator<T>(this);
}
m_enumerator.Reset();
return m_enumerator;
}
目前的函數(shù)實(shí)現(xiàn)經(jīng)過(guò)設(shè)計(jì)的話,它不會(huì)產(chǎn)生GC。然而,問(wèn)題在后面緊緊跟隨。實(shí)現(xiàn)了IEnumerable接口之后。當(dāng)我們使用形如foreach(var t in list)的時(shí)刻,它就會(huì)去調(diào)用list中的繼承于IEnumerator的Current實(shí)現(xiàn):
namespace System.Collections
{
public interface IEnumerator
{
object Current
{
get;
}
bool MoveNext();
void Reset();
}
}
看到這里,它返回的是object,如果我們List中存放的是值類型,那么系統(tǒng)自然就產(chǎn)生了一次box裝箱操作,GC于是悄悄地產(chǎn)生了。
也正是因?yàn)檫@個(gè)原因,微軟后來(lái)加入了泛型的IEnumerator。但是,為了兼容以前的設(shè)計(jì),這個(gè)泛型IEnumerator被設(shè)計(jì)成實(shí)現(xiàn)于之前的IEnumerator,而它的下方增加了同樣的Current的Get方法。
using System;
using System.Collections;
namespace System.Collections.Generic
{
public interface IEnumerator<T> : IEnumerator, IDisposable
{
T Current
{
get;
}
}
}
同樣的設(shè)計(jì)也被用于泛型的IEnumerable,
using System.Collections;
namespace System.Collections.Generic
{
public interface IEnumerable<T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
}
如果我們實(shí)現(xiàn)泛型的IEnumerable和IEnumerator,必須同時(shí)泛型和非泛型的GetEnumerator和Current方法。
那么,問(wèn)題來(lái)了?,F(xiàn)在有兩個(gè)GetEnumerator()方法,兩個(gè)Current的Get方法,究竟該用誰(shuí)的呢?
首先,在實(shí)現(xiàn)的時(shí)候就需要加以區(qū)分:
public IEnumerator<T> GetEnumerator()
IEnumerator IEnumerable.GetEnumerator()
這兩個(gè)實(shí)現(xiàn),你給其中一個(gè)加上public,另外一個(gè)就不能加上public;兩個(gè)函數(shù)至少有一個(gè)需要增加[接口名稱.]這種前綴;那么最終我們?cè)趂oreach期間調(diào)用的就是public的那個(gè)方法。
自然,我們這里為了避免使用到非泛型IEnumerator中的Current方法的object返回形式,我們必須使用將唯一的生存權(quán)留給泛型的GetEnumerator。
同樣,我們也需要在自定義的枚舉器中作出選擇。保留泛型的Current函數(shù)。
struct ABEnumerator<T> : IDisposable, IEnumerator<T>
{
private AB_List<T> m_list;
private int m_idNext;
private T m_current;
public object Current
{
get
{
if (this.m_idNext <= 0)
{
throw new InvalidOperationException();
}
return this.m_current;
}
}
T IEnumerator<T>.Current
{
get
{
return this.m_current;
}
}
...
GC測(cè)試
應(yīng)用于AB_List< int >的foreach
void testABListForeach()
{
for (int i = 0; i < 1000; i++)
{
foreach (var iNum in m_intABList)
{
}
}
}
沒(méi)有產(chǎn)生GC
應(yīng)用于AB_List< int >的GetEnumerator
void testABListGetEmulator()
{
for (int i = 0; i < 1000; i++)
{
var iNum = m_intABList.GetEnumerator();
while (iNum.MoveNext())
{
var t= iNum.Current;
}
}
}
也沒(méi)有產(chǎn)生GC
局限性
本list雖然沒(méi)有GC,但是其使用也有一定的局限性。
最大的局限性是foreach嵌套的問(wèn)題。當(dāng)我們?cè)诤芏嘀厍短字型瑫r(shí)foreach這個(gè)list時(shí),相當(dāng)于需要多個(gè)枚舉器同時(shí)存在。List被設(shè)計(jì)為存儲(chǔ)一定數(shù)目的枚舉器組,以便于多次調(diào)用。(這樣設(shè)計(jì)是為了避免值類型枚舉器被當(dāng)做引用返回時(shí)造成的裝箱問(wèn)題)
不過(guò),一般而言,你應(yīng)該不需要那么多層的foreach嵌套,默認(rèn)允許同時(shí)嵌套5層,如果你需要超過(guò)5層的嵌套,則在ABList的構(gòu)造函數(shù)中傳入更多的層次就可以。
另外一個(gè)小小的限制就是,當(dāng)你使用getEnumerator()時(shí),需要把它們放在using中,大概是這樣:
using (var iNum = m_intABList.GetEnumerator())
{
}
這樣做的目的是在代碼塊結(jié)束時(shí),調(diào)用枚舉器的Dispose方法,這樣可以讓這個(gè)枚舉器變成可重用狀態(tài)。
總結(jié)
Unity系統(tǒng)的泛型List存在的問(wèn)題是:它在finally中回收枚舉器時(shí)執(zhí)行了Box操作。自定義List時(shí),正確實(shí)現(xiàn)泛型格式的IEnumerable、IEnumerator是關(guān)鍵,需要避開(kāi)枚舉單元被Current時(shí),值類型被強(qiáng)制轉(zhuǎn)換成對(duì)象類型的Box操作。
總體上來(lái)說(shuō),這應(yīng)該是一個(gè)高效的,無(wú)GC的List,看官可以嘗試一哈。