2012年1月16日 星期一

ArrayList 集合

集合物件的特性:允許資料項目的增減,可以巡覽集合中的資料項目。

集合的種類

清單、佇列、位元陣列、雜湊表和字典等等都是.NET底下的一種集合,所有集合類別都由以下二個命名空間提供:

1. System.Collections 命名空間所提供的類別

類別 說明
ArrayList 使用大小會視需要動態增加的陣列,實作 IList 介面。
CaseInsensitiveComparer 比較兩個物件是否相等,忽略字串的大小寫。
CaseInsensitiveHashCodeProvider 已過時。使用忽略字串大小寫的雜湊演算法,提供物件的雜湊程式碼。
CollectionBase 提供強式型別集合的 abstract 基底類別。
ReadOnlyCollectionBase 提供強式非泛型唯讀集合的 abstract 基底類別。
Comparer 比較兩個物件是否相等,其中字串比較是區分大小寫的。
DictionaryBase 提供索引鍵/值組配對強式集合的 abstract 基底類別。
Stack 表示簡單之物件的後進先出 (Last-In First-Out,LIFO) 非泛型集合。
Queue 表示物件的先進先出 (FIFO) 集合。
Hashtable 表示根據索引鍵的雜湊程式碼,所整理的索引鍵/值組集合。
SortedList 表示索引鍵/值組配對的集合,這個集合按索引鍵排序,而且可以按索引鍵和索引存取。
BitArray 管理以布林 (Boolean) 表示的位元值之精簡陣列,其中 true 表示位元為開啟 (1),而 false 表示位元為關閉 (0)。
StructuralComparisons 提供物件,用以執行兩個集合物件的結構比較。

2. System.Collections.Specializeds 命名空間所提供的類別

類別 說明
CollectionsUtil 建立忽略字串大小寫的集合。
HybridDictionary 在集合很小時,使用 ListDictionary 來實作 IDictionary,然後在集合變大時,切換至 Hashtable
ListDictionary 使用單向連結串列來實作 IDictionary 。建議用於通常包含 10 個或更少項目的集合。
NameObjectCollectionBase 提供 abstract 基底類別給可以用索引鍵或索引來存取之關聯 String 索引鍵和 Object 值的集合。
NameObjectCollectionBase.KeysCollection 表示集合索引鍵 String 的集合。
NameValueCollection 表示相關 String 索引鍵和 String 值的集合,而這些可以利用索引鍵或索引來存取。
OrderedDictionary 表示根據索引鍵/索引排序的索引鍵/值組集合。
StringCollection 表示字串的集合。
StringDictionary 實作雜湊資料表,其中的索引鍵與資料值均採用強式型別宣告為字串,而不是物件。
StringEnumerator 支援在 StringCollection 上的簡易反覆運算。

ArrayList 類別

ArrayList 是一種基本的集合物件,有著以下特性:

  • 隨著元素逐漸加入,會視需要透過重新配置的方式自動增加容量。
  • 在這個集合的項目是沒有經過排序的,可以儲存任何型別(Object),也接受 null 值,也允許重複的項目。
  • 如果置放實質型別,則會被 Boxing。

ArrayList 方法

底下內容,皆以 ArrayList 來說明集合的基本操作功能。

加入或移除資料項目

ArrayList alCol = new ArrayList();

// 集合項目可以是 value type 或 reference type

// 加入一個項目
string s = "Hello";
alCol.Add(s);
alCol.Add("hi");
alCol.Add(50);
alCol.Add(new object());

// 加入一個陣列
string[] anArray = new string[] { "more", "or", "less" };
alCol.AddRange(anArray);

// 加入一個集合
object[] anotherArray = new object[] { new object(), new ArrayList() };
alCol.AddRange(anotherArray);

// 插入一個項目
alCol.Insert(3, "Hey All");

// 插入一個陣列
string[] moreStrings =  new string[] { "goodnight", "see ya" };
alCol.InsertRange(4, moreStrings);

// 更新一個項目
alCol[3] = "Hi all";

// 移除一個項目
alCol.Add("Hello");
alCol.Remove("Hello");

// 移除第1個項目
alCol.RemoveAt(0);

// 移除前4個項目
alCol.RemoveRange(0, 4);

//判斷是否存在某個項目
if (alCol.Contains("Hello"))
{
    //找出某個item的Index
    int index2 = alCol.IndexOf("Helll");   //non match retrun -1
    int index = alCol.IndexOf("Hello");
    alCol.RemoveAt(index);
}

//清除所有項目
alCol.Clear();

巡訪資料項目(Iterate)

要取得 ArrayList 中的項目,有以下幾種方法:

  • 1. 利用 indexer
    for (int x = 0; x < coll.Count; ++x)
            {
                Console.WriteLine(coll[x]);
            }
  • 2. 利用 foreach : 只要支援 IEnumerable 介面的物件,就可以使用 foreach 語法,簡單地達到巡訪所有 item 的操作
    foreach (object item in coll)
        {
            Console.WriteLine(item);
        }
    
        //若已知集合中的項目皆為同一型別,可以在foreach指定型別,以節省物件轉型的時間
        foreach (string item in coll)
        {
            Console.WriteLine(item);
        }
        
  • 3. 利用 IEnumeratorArrayList 支援 IEnumerable 介面,它允許使用列舉值 (IEnumerator) 來存取串列。
    IEnumerator enumerator = coll.GetEnumerator();  //透過 GetEnumerator 方法,取得 IEnumerator
    
        while (enumerator.MoveNext())                   //列舉值一開始會位於集合中的第一個元素之前
        {
            Console.WriteLine(enumerator.Current);      //Current:取得目前項目
        }
    
        enumerator.Reset();                             //利用Reset, 將 enumerator 重新指向第一個
        

備註

列舉集合中的元素時,並不會獨佔集合的存取權,因此在集合中逐一列舉,本質上不是執行緒安全的程序。 若要確保列舉期間的執行緒安全,必須在整個列舉期間鎖定該集合,也就是必須實作自己的同步處理機制。

集合介面的一致性

在處理集合項目的操作時, .Net 定義了幾組介面,以確保實作時可以呼叫的共通方法。 例如:上面提到的 IEnumerable,該介面提供了巡訪資料的共通方法。

.Net 定義了幾組介面,以確保實作時可以呼叫的共通方法

  • IEnumerable :提供了巡訪資料的共通方法
  • IEnumerator :提供了巡訪資料的共通方法
  • ICollection :確保每個集合都支援取得集合項目的共通方法,以及將集合複製到一個Array物件的共通方法。
  • IList :表示可以個別由索引存取之物件的非泛型集合。
  • IComparer :公開比較兩個物件的方法。
//IEnumerable 介面的組成成員
public interface IEnumerable
{  
    IEnumerator GetEnumerator();
}
//IEnumerator 介面的組成成員
public interface IEnumerator
{  
    object Current { get; }
    bool MoveNext();
    void Reset();
}
//ICollection 介面的組成成員
public interface ICollection
{  
    int Count { get; }                   //取得在 ICollection 中所包含的元素數。
    bool IsSynchronized { get; }         //取得值,指出對 ICollection 的存取是否為同步 (安全執行緒)。

    void CopyTo(Array array,int index)  //從特定的 Array 索引開始,複製 ICollection 項目至 Array。
    IEnumerator GetEnumerator()          //傳回會逐一查看集合的列舉程式。 (繼承自 IEnumerable)。
}
//IList 介面的組成成員
public interface IList
{  
    Object this[int index { get; set; }
    bool IsFixedSize { get; }
    bool IsReadOnly { get; }

    int Add (Object value)
    void Clear ()
    bool Contains (Object value)
    int IndexOf (Object value)
    void Insert (int index,	Object value)
    void Remove (Object value)
    void RemoveAt (int index)
}
//IComparer 介面的組成成員
public interface IComparer
{  
    int Compare (Object x,Object y)
}

排序

ArrayList 類別支援對集合進行排序,只要呼叫它的 Sort 方法即可,例如:

coll.Sort();                            //以預設方法排序
coll.Reverse();                         //反向排序

ArrayList.Sort 共有三個覆載方法。

public virtual void Sort();
public virtual void Sort(IComparer comparer);
public virtual void Sort(int index, int count, IComparer comparer);

不帶參數的 Sort 會依照集合元素本身的比較原則進行排序,也就是集合元素必須實作 IComparable 介面才可以使用這個排序方法。

IComparer 型別參數的 Sort ,就不會根據 item 本身的比較原則,而是依據這個參數當做比較原則。

使用比較子進行比較

若要支援 IComparer 介面,則必須實作 Compare 方法:

int Compare(object x, object y);

Compare 是用來比較兩個物件的大小。其回傳值意義如下:

  • 負值: x 小於 y。
  • 零值: x 等於 y。
  • 正值: x 大於 y。

範例

//定義一個比較子
public class DescendingComparer : IComparer
{
    CaseInsensitiveComparer _comparer = new CaseInsensitiveComparer();
    public int Compare(object x, object y)
    {
        //將二值對調,呼叫CaseInsensitiveComparer.Compare,以比較大小
        return _comparer.Compare(y, x);
    }
}

private void button3_Click(object sender, EventArgs e)
{
    ArrayList coll = new ArrayList();
    coll.Add("First");
    coll.Add("Second");
    coll.Add("Third");
    coll.Add("Fourth");

    coll.Sort();                            //以預設方法排序
    coll.Sort(new DescendingComparer());    //自訂 compare 方法排序
}

實作類別的 IComparable 介面,以支援比較

若要支援 IComparable 介面,則必須實作 CompareTo 方法。

int CompareTo(object obj);

CompareTo 是用來表示目前的執行個體應該排序在比較物件的前面、後面或相同位置。其回傳值意義如下:

  • 負值: 執行個體排序在 obj 前面。
  • 零值: 執行個體與 obj 排序在相同位置。
  • 正值: 執行個體排序在 obj 後面。

範例

public class myAge: IComparable
{
    private int _value;

    public myAge(int age)
    {
        _value = age;
    }
    public int Value()
    {
        return _value;
    }
    public override string ToString()
    {
        return _value.ToString();
    }
    public int CompareTo(object obj)
    {
        if (obj is myAge)
        {
            myAge age = (myAge)obj;
            return _value.CompareTo(age.Value());
        }
        else
            throw new ArgumentException("object is not an Age");
    }
}
private void button5_Click(object sender, EventArgs e)
{
    ArrayList ages = new ArrayList();
    ages.Add(new myAge(10));
    ages.Add(new myAge(40));
    ages.Add(new myAge(30));
    ages.Add(new myAge(20));
    foreach (myAge age in ages)
    {
        Console.WriteLine(age);
    }
    //10 40 30 20
    ages.Sort();
    foreach (myAge age in ages)
    {
        Console.WriteLine(age);
    }
    //10 20 30 40
}

搜尋

下面三種方法都可以用來搜尋 ArrayList 中是否存在某個項目:

  • IndexOf :傳回 ArrayList 中第一個符合搜尋項目的索引。這個方法採用線性搜尋,是一種 O(n) 運算。
  • Contains :判斷某元素是否在 ArrayList 中,此方法會呼叫 Object.Equals 來判斷是否相等。這個方法採用線性搜尋,是一種 O(n) 運算。
  • BinarySearch :使用二進位搜尋演算法來尋找在排序的 ArrayList 中的元素。這個方法是 O(log n) 運算。
ArrayList myAL = new ArrayList();
    for (int i = 0; i <= 4; i++)    // 0 2 4 6 8
        myAL.Add(i * 2);

    int obj = 4;

    // Using Contains
    if (!myAL.Contains(obj))
        Console.WriteLine("({0}) is not found", obj);
    else
    {
        int index1 = myAL.IndexOf(obj);
        Console.WriteLine("({0}) is at index {1}.", obj, index1);
    }

    // Using BinarySearch
    int index2 = myAL.BinarySearch(obj);
    if (index2 < 0)
        Console.WriteLine("({0}) is not found. The next larger object is at index {1}.", obj, ~index2);
    else
        Console.WriteLine("The object to search for ({0}) is at index {1}.", obj, index2);

    //(3) is not found
    //(3) is not found. The next larger object is at index 2.

    //(4) is at index 2.
    //The object to search for (4) is at index 2.

備註

ArrayList.Sort() 這個方法使用 Array.Sort,並採用 QuickSort 演算法。QuickStort 演算法是比較排序 (也稱為不穩定的排序,亦即,如果有兩個項目相同,可能無法保留其順序。),表示「 小於或等於」比較作業會判斷最終排序清單中應該先發生兩個項目中的。不過,如果兩個項目相等,其原本的順序可能不會保留。相反地,穩定排序可以保留相等項目的順序。若要執行穩定排序,您必須實作自訂 IComparer 介面,以搭配使用這個方法的其他多載。

平均而言,這個方法是 O(n log n) 運算,其中 n 為 Count;最壞的狀況是變成 O(n ^ 2) 運算。

IsSynchronized 屬性和 ArrayList.Synchronized 方法

ArrayList 本身並不是一個執行緒安全的物件,若要在多執行緒作業中訪問,就必須自已動手調用 lock 機制以保持執行緒同步。例如:

ArrayList al = new ArrayList();
lock (al.SyncRoot)
{
    al.Add("test1");
    al.Add("test2");
    //....
}

如果使用 ArrayList.Synchronized 方法取得的實體,那麼就不用考慮執行序同步的問題,因為這個實體本身就是執行序安全的。 實際上 ArrayList 內部有實作一個執行序安全的類別, Synchronized 回傳的就是這個類別,它裡面每個屬性都用了 lock 關鍵字來保護執行緒同步。

ArrayList al = new ArrayList();
ArrayList sync_al = ArrayList.Synchronized(al);

sync_al.Add("test1");
sync_al.Add("test2");

總結

  • The .NET Framework supports a variety of collection classes that can be used in different circumstances.
  • The ArrayList is a simple collection of unordered items.
  • The Add and AddRange methods of the ArrayList are used to add items to an ArrayList .
  • The Insert and InsertRange methods of the ArrayList are used to insert items into specific places in a collection.
  • The Remove , RemoveAt , and RemoveRange methods of the ArrayList are used to delete items from a collection.
  • The indexer of the ArrayList can be used to iterate over a collection.
  • The IEnumerable and IEnumerator interfaces can be used to enumerate through a collection as well.
  • The foreach construct in Visual Basic and C# use the IEnumerable interface to concisely iterate over a collection.

沒有留言:

張貼留言