第十章-集合


1>集合类可以组合为集合,存储 Object 类型的元素和泛型集合类。
    List<T>和 ArrayList 是与数组相当的集合类。
    对象类型的集合位于 System.Collections 命名空间;
    泛型集合类位于 System.Collections.Generic 命名空间;
    专用于特定类型的集合类位于 System.Collections.Specialized 命名空间。

  集合可以根据集合类执行的接口组合为列表、集合和字典。
 
2>列表
(1)动态列表提供了类 ArrayList 和 List<T>。System.Collections.Generic
命名空间中的类 List<T>的用法非常类似于 System.Collections 命名空间中的 ArrayList 类。这个类实现了 IList、 ICollection 和 IEnumerable 接口。
(2)创建列表
1)调用默认的构造函数,就可以创建列表对象。在泛型类 List<T>中,必须在声明中为列表的值指定类型。
2)ArrayList 是一个非泛型列表,可以将任意 Object 类型作为其元素接受。

```
ArrayList objectList = new ArrayList();
List<int> intList = new List<int>();
List<Racer> racers = new List<Racer>();
```

3)创建了一个容量为 10 个元素的集合。如果该容量不足以容纳要添加的元素,就把集合的大小重新设置为 20,或 40,每次都是原来的 2 倍.

```
ArrayList objectList = new ArrayList(10);
List<int> intList = new List<int>(10);
```

使用 Capacity 属性可以获取和设置集合的容量。

```
objectList.Capacity = 20;
intList.Capacity = 20;
```

容量与集合中元素的个数不同。集合中元素的个数可以用 Count 属性读取。当然,容量总是大于或等于元素个数。只要不把元素添加到列表中,元素个数就是 0。   

```
Console.WriteLine(intList.Count);
```

1,添加元素
使用 Add()方法可以给列表添加元素,如下所示。实例化的泛型类型用 Add()方法定义了第一个参数的类型:

```
List<int> intList = new List<int>();
intList.Add(1);
intList.Add(2);
List<string> stringList = new List<string>();
stringList.Add("one");
stringList.Add("two");
```

变量 racers 定义为类型 List<Racer>。使用 new 操作符创建相同类型的一个新对象。因为类 List<T>用具体类 Racer 来实例化,所以现在只有 Racer 对象可以用 Add()方法添加。
创建了 4 个一级方程式赛手,并添加到集合中。

```
List<Racer> racers = new List<Racer>(20);
Racer graham = new Racer("Graham", "Hill", "UK", 14);
Racers.Add(graham);
Racer emerson = new Racer("Emerson", "Fittipaldi", "Brazil", 14);
Racers.Add(emerson);
Racer mario = new Racer("Mario", "Andretti", "USA", 12);
Racers.Add(Mario);
racers.Add(new Racer("Michael", "Schumacher", "Germany", 91));
racers.Add(new Racer("Mika", "Hakkinen", "Finland", 20));
```


使用 List<T>类的 AddRange()方法,可以一次给集合添加多个元素。 AddRange()方法的参数是 IEnumerable<T>类型的对象,所以也可以传送一个数组,如下所示:

```
racers.AddRange(new Racer[] {
new Racer("Niki", "Lauda", "Austria", 25)
new Racer("Alian", "Prost", "France", 51 } );
```

如果在实例化列表时知道集合的元素个数,也可以将执行 IEnumerable<T>的任意对象传送给类的构造函数。

```
List<Racer> racers = new List<Racer> (new Racer[] {
new Racer("Jochen", "Rindt", "Austria", 6)
new Racer("Ayrton", "Senna", "Brazil", 41 } );
```

2,插入元素
使用 Insert()方法可以在指定位置插入元素:

```
racers. Insert(3, new Racer("Phil", "Hill", "USA", 3));
```

方法 InsertRange()提供了插入大量元素的容量,类似于前面的 AddRange()方法。

3,访问元素
执行了 IList 和 IList<T>接口的所有类都提供了一个索引器,所以可以使用索引器,通过传送元素号来访问元素。第一个元素可以用索引值 0 来访问。

```
Racer r1 = racers[3];
```

可以用 Count 属性确定元素个数,再使用 for 循环迭代集合中的每个元素,使用索引器访问每一项:

```
for (int i=0; i<racers.Count; i++)
{
Console.WriteLine(racers[i]);
}
```

提示:可以通过索引访问的集合类有 ArrayList、 StringCollection 和 List<T>
List<T>执行了接口 IEnumerable,所以也可以使用 foreach 语句迭代集合中的元素。

```
foreach (Racer r in racers)
{
Console.WriteLine(r);
}
```

List<T>类还提供了 ForEach()方法,它用 Action<T>参数声明。 ForEach 迭代集合中的每一项,调用传送作为每一项的参数的方法。

```
public void ForEach(Action<T> action);
```

Console.WriteLine()方法的一个重载版本将 Object 作为参数,所以可以将这个方法的地址传送给 ForEach()方法,把集合中的每个赛手写入控制台:

```
racers.ForEach(Console.WriteLine);
```

编写一个匿名方法,它将 Racer 对象作为参数。这里,格式 A 由 IFormattable接口的 ToString()方法用于显示赛手的所有信息:

```
racers.ForEach(
delegate(Racer r)
{
Console.WriteLine("{0:A}", r);
});
```

4,删除元素
删除元素时,可以利用索引,或传送要删除的元素。下面的代码把 3 传送给 RemoveAt(),删除第 4 个元素:

```
racers. RemoveAt(3);
```

方法 RemoveRange()可以从集合中删除许多元素。它的第一个参数指定了开始删除的元素索引,第二个参数指定了要删除的元素个数。

```
int index = 3;
int count = 5;
racers. RemoveRange (index, count)
```

要从集合中删除有指定特性的所有元素,可以使用 RemoveAll()方法。这个方法使用下面搜索元素时将讨论的 Predicate<T>参数。要删除集合中的所有元素,可以使用ICollection<T>接口定义的 Clear()方法。

5,排序
List<T>类可以对元素排序。 Sort()方法定义了几个重载方法。可以传送给它的参数有泛型委托 Comparison<T>、泛型接口 IComparer<T>、泛型接口
IComparer<T>和一个范围值。
public void List<T>. Sort();
public void List<T>. Sort(Comparison<T>);
public void List<T>. Sort(IComparer<T>);
public void List<T>. Sort(Int32, Int32, IComparer<T>);
只有集合中的元素执行了接口 IComparable,才能使用不带参数的 Sort()方法。
类 Racer 实现了 IComparable<T>接口,可以按姓氏对赛手排序:

```
racers. Sort();
racers.ForEach(Console.WriteLine);
```

类 RacerComparer 给 Racer 类型执行了接口 IComparer<T>。这个类允许按名字、姓氏、国籍或获胜人数排序。排序的种类用内部枚举类型 CompareType 定义。 CompareType 用类RacerComparer 的构造函数设置。接口 IComparer<Racer>定义了排序所需的方法 Compare()。在这个方法的实现代码中,使用了 string 和 int 类型的 CompareTo()方法。

可以对类 RacerComparer 的一个实例使用 Sort()方法。传送枚举 RacerComparer.CompareType.Country,按属性 Country 对集合排序:

```
CompareType.Country,按属性 Country 对集合排序:
```

排序的另一种方式是使用重载的 Sort()方法,它需要一个 Comparison<T>委托:

```
public void List<T> Sort (Comparison<T>);
```

Comparison<T>是一个方法的委托,该方法有两个 T 类型的参数,返回类型为 int。参数值相等,该方法就返回 0。如果第一个参数比第二个小,就返回小于 0 的值;否则,返回大于 0 的值。

```
public delegate int Comparison<T>(T x, T y);
```

方法调用:

```
racers.Sort(delegate(Racer r1, Racer r2) {
return r2.Wins.CompareTo(r1.Wins); });
```

也可以调用 Reverse()方法,倒转整个集合的顺序。


3>队列
(1)队列是其元素以先进先出(FIFO)的方式来处理的集合。先放在队列中的元素会先读取.
    可以为一组队列建立一个数组,数组中的一项代表一个优先级。在每个数组项中,都有一个队列,其处理按照 FIFO 的方式进行.
(2)队列与列表的主要区别是队列没有执行 IList 接口。所以不能用索引器访问队列。队列只允许添加元素,该元素会放在队列的尾部(使用 Enqueue()方法),从队列的头部获取元素(使用 Dequeue()方法)。
1,Enqueue()方法在队列的一端添加元素, Dequeue()方法在
队列的另一端读取和删除元素。用 Dequeue()方法读取元素,将同时从队列中删除该元素。再调用一次 Dequeue()方法,会删除队列中的下一项.

2,下面的文档管理应用程序示例演示了 Queue<T>类的用法。使用一个线程将文档添加到队列中,用另一个线程从队列中读取文档,并处理它们。
存储在队列中的项是 Document 类型。 Document 类定义了标题和内容:

```
public class Document
{
private string title;
public string Title
{
get
{
return title;
}
}
private string content;
public string Content
{
get
{
return content;
}
}
public Document(string title, string content)
{
this.title = title;
this.content = content;
}
}
```

DocumentManager 类是 Queue<T>类外面的一层。 DocumentManager 类定义了如何处理文档:用 AddDocument()方法将文档添加到队列中,用 GetDocument()方法从队列中获得文档。
在 AddDocument() 方 法 中 , 用 Enqueue() 方 法 把 文 档 添 加 到 队 列 的 尾 部 。 在GetDocument()方法中,用 Dequeue()方法从队列中读取第一个文档。多个线程可以同时访
问 DocumentManager,所以用 lock 语句锁定对队列的访问。

4>栈
(1)栈是与队列非常类似的另一个容器,只是要使用不同的方法访问栈。最后添加到栈中的元素会最先读取。栈是一个后进先出(LIFO)容器。
1)用 Push()方法在栈中添加元素,用 Pop()方法获取最近添加的元素.
  与 Queue 类相同,非泛型类 Statck 也执行了 ICollection、 IEnumerable 和 ICloneable 接口;泛型类 Statck<T>实现了 IEnumerable<T>、 ICollection 和 IEnumerable 接口。

在下面的例子中,使用 Push()方法把三个元素添加到栈中。在 foreach 方法中,使用IEnumerable 接口迭代所有的元素。栈的枚举器不会删除元素,只会逐个返回元素.

```
Stack<char> alphabet = new Stack<char>();
alphabet.Push('A');
alphabet.Push('B');
alphabet.Push('C');
foreach (char item in alphabet)
{
Console.Write(item);
}
Console.WriteLine();
```

元素的读取顺序是从最后一个添加到栈中的元素开始到第一个元素.

用枚举器读取元素不会改变元素的状态。使用 Pop()方法会从栈中读取每个元素,然后删除它们。这样,就可以使用 while 循环迭代集合,检查 Count 属性,确定栈中是否还有元素:
Stack<char> alphabet = new Stack<char>();
alphabet.Push('A');
alphabet.Push('B');
alphabet.Push('C');

```
Console.Write("First iteration: ");
```


foreach (char item in alphabet)
{
Console.Write(item);
}
Console.WriteLine();

```
Console.Write("Second iteration: ");
while (alphabet.Count > 0)
{
Console.Write(alphabet.Pop());
}
Console.WriteLine();
```


结果是两个 CBA。在第二次迭代后,栈变空,因为第二次迭代使用了 Pop()方法:
First iteration: CBA
Second iteration: CBA

5>链表
(1)LinkedList<T>集合类没有非泛型集合的类似版本。 LinkedList<T>是一个双向链表,其元素指向它前面和后面的元素
链表的优点是,如果将元素插入列表的中间位置,使用链表会非常快。在插入一个元素时,只需修改上一个元素的 Next 引用和下一个元素的 Previous 引用,使它们引用所插入的元素。在 List<T>和 ArrayList 类中,插入一个元素,需要移动该元素后面的所有元素。
链表也有缺点。链表的元素只能一个接一个地访问,这需要较长的时间来查找
位于链表中间或尾部的元素。
链表不仅能在列表中存储元素,还可以给每个元素存储下一个元素和上一个元素的信息 。 这 就 是 LinkedList<T> 包 含 LinkedListNode<T> 类 型 的 元 素 的 原 因 。 使 用LinkedListNode<T>类,可以获得列表中的下一个元素和上一个元素。

    

```
表 10-4 描述了LinkedListNode<T>的属性。
LinkedListNode<T>的属性                   说 明
List                             返回与节点相关的 LinkedList<T>
Next         返回当前节点之后的节点。其返回类型是LinkedListNode<T>
Previous                             返回当前节点之前的节点
Value                         返回与节点相关的元素,其类型是 T
```


类 LinkedList<T>执行了 IEnumerable<T>、 ICollection<T>、 ICollection、 IEnumerable、ISerializable 和 IDeserializationCallback 接口。


6> 有序表
(1)如果需要排好序的表,可以使用 SortedList<TKey, TValue>。 这个类按照键给元素排序。
创建了一个有序表,其中键和值都是 string 类型。默认的构造函数创建了
一个空表,再用 Add()方法添加两本书。使用重载的构造函数,可以定义有序表的容量,传送执行了 IComparer<TKey>接口的对象,用于给有序表中的元素排序。
Add()方法的第一个参数是键(书名),第二个参数是值( ISBN 号)。
除了使用 Add()
方法之外,还可以使用索引器将元素添加到有序表中。索引器需要把键作为索引参数。如
果键已存在,那么 Add()方法就抛出一个 ArgumentExeption 类型的异常。如果索引器使用相同的键,就用新值替代旧值。

```
SortedList<string, string> books = new SortedList<string, string>();
books.Add(".NET 2.0 Wrox Box", "978-0-470-04840-5");
books.Add("Professional C# 2005", "978-0-7645-7534-1");
books["Beginning Visual C# 2005"] = "978-0-7645-4382-1";
books["Professional C# with .NET 3.0"] = "978-0-470-12472-7";
```

可以使用 foreach 语句迭代有序表。枚举器返回的元素是 KeyValuePair<TKey, TValue>类型,其中包含了键和值。键可以用 Key 属性访问,值用 Value 属性访问。

```
foreach (KeyValuePair<string, string> book in books)
{
Console.WriteLine("{0}, {1}", book.Key, book.Value);
}
```

迭代语句会按键的顺序显示书名和 ISBN 号:
.NET 2.0 Wrox Box, 978-0-470-04840-5
Beginning Visual C# 2005, 978-0-7645-4382-1
Professional C# 2005, 978-0-7645-7534-1
Professional C# with .NET 3.0, 978-0-470-12472-7

也可以使用 Values 和 Keys 属性访问值和键。 Values 属性返回 IList<TValue>, Keys 属性返回 IList<TKey>,所以,可以在 foreach 中使用这些属性:

```
foreach (string isbn in books.Values)
{
Console.WriteLine(isbn);
}
foreach (string title in books.Keys)
{
Console.WriteLine(title);
}
```

第一个循环显示值,第二个循环显示键:
978-0-470-04840-5
978-0-7645-4382-1
978-0-7645-7534-1
978-0-470-12472-7
.NET 2.0 Wrox Box
Beginning Visual C# 2005
Professional C# 2005
Professional C# with .NET 3.0

SortedList<T>类型的方法类似于本章前面介绍的其他集合。见表 10-7。区别是 SortedList<T>需要一个键和一个值.

注意:除了泛型 SortedList<TKey, TValue>之外,还有一个对应的非泛型有序表 SortedList。

7>字典
(1)字典表示一种非常复杂的数据结构,这种数据结构允许按照某个键来访问元素。字典也称为映射或散列表。字典的主要特性是能根据键快速查找值。也可以自由添加和删除元素,这有点像 List<T>,但没有在内存中移动后续元素的性能开销.
可以使用的最主要的类是 Dictionary<TKey, TValue>。
这个类的属性和方法与上面的 SortedList<TKey, TValue>几乎完全相同,这里不再赘述。

1)键的类型
用作字典中键的类型必须重写 Object 类的 GetHashCode()方法。只要字典类需要确定元素的位置,就要调用 GetHashCode()方法。 GetHashCode()方法返回的 int 由字典用于计算放置元素的索引。

GetHashCode()方法的实现代码必须满足如下要求:
● 相同的对象应总是返回相同的值。
● 不同的对象可以返回相同的值。
● 应执行得比较快,计算的开销不大。
● 不能抛出异常。
● 应至少使用一个实例字段。
● 散列码值应平均分布在 int 可以存储的整个数字区域上。
● 散列码最好在对象的生存期中不发生变化


提示:字典的性能取决于 GetHashCode()方法的实现代码。



总结:数组的大小是固定的,但可以使用列表作为动态增长的集合。队列以先进先出的方式访问元素,栈以后进先出的方式访问元素。链表可以快速插入和删除元素,但搜索操作比较慢。通过键和值可以使用字典,它的搜索和插入操作比较快。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值