c#集合解析

什么是集合(collection)?

提供了一种结构化组织任意对象的方式,从.NET 的角度看,所谓的集合可以定义为一种对象,这种对象实现一个或者多个System.Collections.ICollection、 System.Collections.IDictionary和System.Collections.IList接口。这一定义把 System.Collections名称空间中的“内置”集合划分成了三种类别:

  *  有序集合:仅仅实现ICollection接口的集合,在通常情况下,其数据项目的插入顺序控制着从集合中取出对象的的顺序。 System.Collections.Stack和 System.Collections.Queue类都是ICollection集合的典型例子。
    *  索引集合:实现Ilist的集合,其内容能经由从零开始的数字检索取出,就象数组一样。System.Collections.ArrayList对象是索引集合的一个例子。
    *  键式集合:实现 IDictionary 接口的集合,其中包含了能被某些类型的键值检索的项目。IDictionary集合的内容通常按键值方式存储,可以用枚举的方式排序检索。 System.Collections.HashTable类实现了IDictionary 接口。

以OOP (面向对象编程)的术语来说,上面以接口这种方式构造对象的功能,就是多态技术。

System.Collections 命名空间包含接口和类,这些接口和类定义各种对象(如列表、队列、位数组、哈希表和字典)的集合。
System.Collections.Generic 命名空间包含定义泛型集合的接口和类,泛型集合允许用户创建强类型集合,它能提供比非泛型强类型集合更好的类型安全性和性能。
System.Collections.Specialized 命名空间包含专用的和强类型的集合,例如,链接的列表词典、位向量以及只包含字符串的集合。

在System.Collections命名空间中提供了许多接口:

      • IEnumerable循环集合项目
      • ICollection可以获取集合中项目个数
      • IList项目列表
      • IDictionary提供了键码索引

 


  • ArrayList
  • SortedList
  • Queue,Stack
  • HashTable
  • NameValueCollection
  • SortedList
  • 泛型集合

什么是ArrayList?

  一种正常数组和集合的混合类型(动态数组,就是Array的复杂版本)

优点:动态的增加和减少元素

    实现了ICollection和IList接口

    灵活的设置数组的大小

缺点:ArrayList负载比传统数组更大而且没有实现严格的类型化,也就可以接受任何转换为System.Object的对象,存在类型安全和效率问题(面试点)

如何使用ArrayList 

 1 ArrayList al = new ArrayList();

 2  al.Add(100);//单个添加

 3  foreach (int number in new int[6] { 9, 3, 7, 2, 4, 8 })

 4  {

 5      al.Add(number);//集体添加方法一

 6  }

 7  al.AddRange(new int[2] { 11, 12 });//集体添加方法二

 8  ArrayList al2 = new ArrayList(al.GetRange(1, 3));//新ArrayList只取一部份

 9  foreach (int i in al)//不要强制转换

10  {

11      Console.WriteLine(i);

12  }

13  for (int i = 0; i < al2.Count; i++)//数组是length

14  {

15      int number = (int)al2[i];//一定要强制转换

16      Console.WriteLine(number);

17  }

18  IEnumerator ie=al.GetEnumerator();

19  while(ie.MoveNext())

20  {

21       Console.Write(ie.Curret.ToString()+" ");

22  }

23  Int32[] values = (Int32[])al.ToArray(typeof(Int32));//返回ArrayList包含的数组

24 View Code
View Code

ArrayList重要的方法和属性

 1 public ArrayList()

 2     {

 3         this._items = emptyArray;

 4     }

 5     public ArrayList(ICollection c)

 6     {

 7         if (c == null)

 8         {

 9             throw new ArgumentNullException("c", Environment.GetResourceString("ArgumentNull_Collection"));

10         }

11         int count = c.Count;

12         if (count == 0)

13         {

14             this._items = emptyArray;

15         }

16         else

17         {

18             this._items = new object[count];

19             this.AddRange(c);

20         }

21     }

22     public ArrayList(int capacity)

23     {

24         if (capacity < 0)

25         {

26             throw new ArgumentOutOfRangeException("capacity", Environment.GetResourceString("ArgumentOutOfRange_MustBeNonNegNum", new object[] { "capacity" }));

27         }

28         if (capacity == 0)

29         {

30             this._items = emptyArray;

31         }

32         else

33         {

34             this._items = new object[capacity];

35         }

36     } public override int Add(object obj)

37         {

38             int num = this._list.Add(obj);

39             base._version++;

40             return num;

41         }
View Code

 IsSynchronized属性,ArrayList.Synchronized方法
1-》IsSynchronized属性指示当前的ArrayList实例是否支持线程同步 2-》而ArrayList.Synchronized静态方法则会返回一个ArrayList的线程同步的封装
1-》如果使用非线程同步的实例,那么在多线程访问的时候,需要自己手动调用lock来保持线程同步,例如:
ArrayList list = new ArrayList();
//...
lock( list.SyncRoot ) //当ArrayList为非线程包装的时候,SyncRoot属性其实就是它自己,但是为了满足ICollection的SyncRoot定义,这里还是使用SyncRoot来保持源代码的规范性
{
list.Add( “Add a Item” );
}
2-》如果使用ArrayList.Synchronized方法返回的实例,那么就不用考虑线程同步的问题,这个实例本身就是线程安全的,实际上ArrayList内部实现了一个保证线程同步的内部类,ArrayList.Synchronized返回的就是这个类的实例,它里面的每个属性都是用了lock关键字来保证线程同步。

但是,使用这个方法(ArrayList.Synchronized)并不能保证枚举的同步,例如,一个线程正在删除或添加集合项,而另一个线程同时进行枚举,这时枚举将会抛出异常。所以,在枚举的时候,你必须明确使用 SyncRoot 锁定这个集合。

 ArrayList与数组转换

 1 ArrayList List = new ArrayList();

 2 List.Add(1);

 3 List.Add(2);

 4 List.Add(3);

 5 Int32[] values = (Int32[])List.ToArray(typeof(Int32));

 6 

 7 ArrayList List = new ArrayList();

 8 List.Add(1);

 9 List.Add(2);

10 List.Add(3);

11 Int32[] values = new Int32[List.Count];

12 List.CopyTo(values);

13 

14 

15 //往数组中添加不同类型的元素

16 ArrayList List = new ArrayList();

17 List.Add( “string” );

18 List.Add( 1 );

19 object[] values = List.ToArray(typeof(object)); //正确

20 string[] values = (string[])List.ToArray(typeof(string)); //错误
View Code

ArrayList最佳使用建议
    这一节我们来讨论ArrayList与数组的差别,以及ArrayList的效率问题
  (1)ArrayList是Array的复杂版本,ArrayList内部封装了一个Object类型的数组,从一般的意义来说,它和数组没有本质的差别,甚至于ArrayList的许多方法,如Index、IndexOf、Contains、Sort等都是在内部数组的基础上直接调用Array的对应方法。
  (2)内部的Object类型的影响对于一般的引用类型来说,这部分的影响不是很大,但是对于值类型来说,往ArrayList里面添加和修改元素,都会引起装箱和拆箱的操作,频繁的操作可能会影响一部分效率。消除这个影响是没有办法的,除非你不用它,否则就要承担一部分的效率损失,不过这部分的损失不会很大。
  (3)数组扩容,这是对ArrayList效率影响比较大的一个因素。每当执行Add、AddRange、Insert、InsertRange等添加元素的方法,都会检查内部数组的容量是否不够了,如果是,它就会以当前容量的两倍来重新构建一个数组,将旧元素Copy到新数组中,然后丢弃旧数组,在这个临界点的扩容操作,应该来说是比较影响效率的。
     例1:比如,一个可能有200个元素的数据动态添加到一个以默认16个元素大小创建的ArrayList中,将会经过:
  16*2*2*2*2 = 256四次的扩容才会满足最终的要求,那么如果一开始就以:ArrayList List = new ArrayList( 210 );的方式创建ArrayList,不仅会减少4次数组创建和Copy的操作,还会减少内存使用。
(4)频繁的调用IndexOf、Contains等方法(Sort、BinarySearch等方法经过优化,不在此列)引起的效率损失
首先,我们要明确一点,ArrayList是动态数组,它不包括通过Key或者Value快速访问的算法,所以实际上调用IndexOf、Contains等方法是执行的简单的循环来查找元素,所以频繁的调用此类方法并不比你自己写循环并且稍作优化来的快,如果有这方面的要求,建议使用Hashtable或SortedList等键值对的集合。


 队列(Queue)栈(Stack):

  System.Collections.Stack 和 System.Collections.Queue 类,两者仅仅实现了ICollection 接口,按照存储项目加到集合的顺序保存System.Object类型的项目。对象只能按其加入顺序从集合中检索:堆栈是后进先出,而队列则是先进先出。通常情况下,你在以下场合可以考虑采用以上这些集合:

    *   接收和处理集合内项目时顺序比较重要。
    *   你能在处理项目之后丢弃它。
    *   你不需要访问集合中的任意项目。

 1 队列

 2 Queue qu = new Queue();

 3 Queue qu2 = new Queue();

 4 foreach (int i in new int[4] { 1, 2, 3, 4 })

 5 {

 6     qu.Enqueue(i);//入队

 7     qu2.Enqueue(i);

 8 }

 9 

10 foreach (int i in qu)

11 {

12     Console.WriteLine(i);//遍历

13 }

14 

15 qu.Dequeue();//出队

16 Console.WriteLine("Dequeue");

17 foreach (int i in qu)

18 {

19     Console.WriteLine(i);

20 }

21 

22 qu2.Peek();//返回位于 Queue 开始处的对象但不将其移除。

23 Console.WriteLine("Peek");

24 foreach (int i in qu2)

25 {

26     Console.WriteLine(i);

27 }

28 

29 30 Stack sk = new Stack();

31 Stack sk2 = new Stack();

32 foreach (int i in new int[4] { 1, 2, 3, 4 })

33 {

34     sk.Push(i);//入栈

35     sk2.Push(i);

36 }

37 

38 foreach (int i in sk)

39 {

40     Console.WriteLine(i);//遍历

41 }

42 

43 sk.Pop();//出栈

44 Console.WriteLine("Pop");

45 foreach (int i in sk)

46 {

47     Console.WriteLine(i);

48 }

49 

50 sk2.Peek();//弹出最后一项不删除

51 Console.WriteLine("Peek");

52 foreach (int i in sk2)

53 {

54     Console.WriteLine(i);

55 }
View Code

HashTable:

  System.Collections.HashTable集合实现了IDictionary 和 Icollection,能用来存储多种类型的对象连同关联的唯一字符串键值。在HashTable集合中的项目按照源自其键值的哈希代码所确定的顺序存储。集合内每个对象的键值都必须唯一,而其哈希代码则不一定唯一。
什么是哈希代码?哈希代码实质上就是从一块数据中消除所有冗余部分之后的结果,它主要起到对数据辅助分类或排序的作用。当某个项目加入集合时,HashTable即调用键值的GetHashCode方法,由于所有的类都是从 System.Objec继承的,所以调用该方法即可确定该类的哈希代码并且按该代码排序存储。你可以强迫使用定制的哈希函数,方法有二,一是重载类的 GetHashCode方法,二是向 HashTable构造器传递实现了System.Collections.IHashcodeProvider接口的对象,在这种情况下,该对象将用于为所有加入集合的键值产生哈希代码。

从性能的角度看,因为键值搜索仅限于具有同样哈希代码的键值,所以HashTable能够很快地从集合中检索任意一个元素,从而减少了必须通过检查以发现匹配的键值的数量。然而,因为插入到集合中的每个对象-键值对都必须产生相应的哈希代码,所以项目插入的代价就有点高了。因此,HashTable 主要运用在按照任意键值反复检索大量相对静态的数据这一场合下

Add方法参数都是object类型

对哈希表进行排序
     对哈希表进行排序在这里的定义是对key/value键值对中的key按一定规则重新排列,但是实际上这个定义是不能实现的,因为我们无法直接在 Hashtable进行对key进行重新排列,如果需要Hashtable提供某种规则的输出,可以采用一种变通的做法:

ArrayList akeys=new ArrayList(ht.Keys); //别忘了导入System.Collections
akeys.Sort(); //按字母顺序进行排序
foreach(string skey in akeys)
{
    Console.Write(skey + ":");
    Console.WriteLine(ht[skey]);//排序后输出
}
 1 Hashtable ht = new Hashtable(); //创建一个Hashtable实例

 2 ht.Add("E", "e");//添加key/value键值对

 3 ht.Add("A", "a");

 4 ht.Add("C", "c");

 5 ht.Add("B", "b");

 6 string s = (string)ht["A"];

 7 if (ht.Contains("E")) //判断哈希表是否包含特定键,其返回值为true或false

 8     Console.WriteLine("the E key:exist");

 9 ht.Remove("C");//移除一个key/value键值对

10 Console.WriteLine(ht["A"]);//此处输出a

11 ht.Clear();//移除所有元素

12 Console.WriteLine(ht["A"]); //此处将不会有任何输出

13 Console.ReadKey();
View Code

NameValueCollection:

System.Collections.Specialized.NameValueCollection 最有趣的地方在于它能包含关联同一键值的多个项目(允许出现相同的键,值会用逗号连起来),这正是它与其他内建集合的差别所在。除此以外,它在功能上类似HashTable,按照源自每一项目键值的哈希代码对项目排序从而也具有类同的优缺点。用处:写自定义控件存储键值一般用这个


 ListDictionary 和 HybridDictionary:

ListDictionary 和 HybridDictionary 类归属于System.Collections.Specialized。它们都在按照唯一键值的原则来组织项目,而且都实现了 IDictionary 和 ICollection 。ListDictionary在内部以链表的方式存储项目,建议用在不会增长超过10个项目的集合中。HybridDictionary采用一个内部链 表(实际上就是ListDictionary)作为小集合,当集合变得足够大(超过10个项目)以至于链表实现效率降低时就会转换为HashTable。


 StringCollection 和 StringDictionary:

System.Collections.Specialized.StringCollection 和 System.Collections.Specialized.StringDictionary 都对存储字符串的集合进行了优化。 StringCollection实现了 IList 和 ICollection 而且实质上就是ArrayList,只不过实现了强烈的类型化仅仅接受字符串而已。StringCollection最理想的应用场合是经常更新或增加的 少量数据,而StringDictionary则最适用于不经常增加项目到诸如HashTable之类集合中的大量数据。


 SortedList:

System.Collections.SortedList,它实现了IDictionary和ICollection接口,是最基本的排序集 合,与Vb6下的Collection对象非常类似。 SortedList存储对象并按照关联的键值对这些存储对象排序。它们也是同时支持索引数字和键对象检索的唯一内建的.NET集合,与哈希表类似,区别在于SortedList中的Key数组排好序的

 1 SortedList sl = new SortedList();

 2 sl["c"] = 41;

 3 sl["a"] = 42;

 4 sl["d"] = 11;

 5 sl["b"] = 13;

 6 

 7 foreach (DictionaryEntry element in sl)

 8 {

 9     string s = (string)element.Key;

10     int i = (int)element.Value;

11     Console.WriteLine("{0},{1}", s, i);

12 }
View Code

 Dictionary 泛型集合

泛型最常见的用途是泛型集合,命名空间System.Collections.Generic 中包含了一些基于泛型的集合类,使用泛型集合类可以提供更高的类型安全性,还有更高的性能,避免了非泛型集合的重复的装箱和拆箱。
    很多非泛型集合类都有对应的泛型集合类,下面是常用的非泛型集合类以及对应的泛型集合类:
非泛型集合类 泛型集合类
ArrayList List<T>
HashTable DIctionary<T>
Queue Queue<T>
Stack Stack<T>
SortedList SortedList<T>
 
我们用的比较多的非泛型集合类主要有 ArrayList类 和 HashTable类。我们经常用HashTable 来存储将要写入到数据库或者返回的信息,在这之间要不断的进行类型的转化,增加了系统装箱和拆箱的负担,如果我们操纵的数据类型相对确定的化  用 Dictionary<TKey,TValue> 集合类来存储数据就方便多了,例如我们需要在电子商务网站中存储用户的购物车信息( 商品名,对应的商品个数)时,完全可以用 Dictionary<string, int> 来存储购物车信息,而不需要任何的类型转化。

    下面是简单的例子,包括声明,填充键值对,移除键值对,遍历键值对
 1 Dictionary<string, string> myDic = new Dictionary<string, string>();

 2 myDic.Add("aaa", "111");

 3 myDic.Add("bbb", "222");

 4 myDic.Add("ccc", "333");

 5 myDic.Add("ddd", "444");

 6 //如果添加已经存在的键,add方法会抛出异常

 7 try

 8 {

 9     myDic.Add("ddd", "ddd");

10 }

11 catch (ArgumentException ex)

12 {

13     Console.WriteLine("此键已经存在:" + ex.Message);

14 }

15 //解决add()异常的方法是用ContainsKey()方法来判断键是否存在

16 if (!myDic.ContainsKey("ddd"))

17 {

18     myDic.Add("ddd", "ddd");

19 }

20 else

21 {

22     Console.WriteLine("此键已经存在:");

23 

24 }

25 

26 //而使用索引器来负值时,如果建已经存在,就会修改已有的键的键值,而不会抛出异常

27 myDic["ddd"] = "ddd";

28 myDic["eee"] = "555";

29 

30 //使用索引器来取值时,如果键不存在就会引发异常

31 try

32 {

33     Console.WriteLine("不存在的键\"fff\"的键值为:" + myDic["fff"]);

34 }

35 catch (KeyNotFoundException ex)

36 {

37     Console.WriteLine("没有找到键引发异常:" + ex.Message);

38 }

39 //解决上面的异常的方法是使用ContarnsKey() 来判断时候存在键,如果经常要取健值得化最好用 TryGetValue方法来获取集合中的对应键值

40 string value = "";

41 if (myDic.TryGetValue("fff", out value))

42 {

43     Console.WriteLine("不存在的键\"fff\"的键值为:" + value);

44 }

45 else

46 {

47     Console.WriteLine("没有找到对应键的键值");

48 }

49 

50 //下面用foreach 来遍历键值对

51 //泛型结构体 用来存储健值对

52 foreach (KeyValuePair<string, string> kvp in myDic)

53 {

54     Console.WriteLine("key={0},value={1}", kvp.Key, kvp.Value);

55 }

56 //获取值得集合

57 foreach (string s in myDic.Values)

58 {

59     Console.WriteLine("value={0}", s);

60 }

61 //获取值得另一种方式

62 Dictionary<string, string>.ValueCollection values = myDic.Values;

63 foreach (string s in values)

64 {

65     Console.WriteLine("value={0}", s);

66 }
View Code

 

你可能感兴趣的:(C#)