一、简介
集合是程序设计中非常重要的一种数据结构,这里说明了我在学习集合的一些总结与心得。
二、集合接口
要说集合,首先得说集合的借口,弄懂了集合的接口,就能拥有一个宏观整体的概念,开始的时候抓住一个实际的集合就开始啃经常局限在一些细枝末节上浪费了大把时间。
集合接口详解
某一个集合能提供什么样的功能就要先看它实现了哪一个接口,例如ICollection<T>接口定义了Count属性用来获得集合中的元素个数,List<T>继承了该接口那么List<T>就拥有Count属性,我们能通过List<T>的Count属性来获得一个List中元素的个数,搞懂了所有的这些接口再看集合就会发现都是水到渠成的感觉,不会有啥一头雾水的概念。想要知道某一个具体的集合实现了哪些接口可以去集合的定义中查看。
下表(来自《C#高级编程》)中说明了一些接口的功能:
以下是接口的定义与详细解释,根据接口的定义对比上表就能清楚的了解每个接口能够实现的功能,整理自MSDN,但并非直接搬运。
····················································································································································································
IEnumerable<T>
IEnumerable<T>是泛型集合中的基接口,List<T>,Dictionary<TKey,TValue>,Stack<T>和其他泛型集合(如ObservableCollection<T>和ConcurrentStack<T>IEnumerable<T>
ICollection<T>)都继承自IEnumerable<T>接口。
方法:
GetEnumerator()
该接口只有一个方法返回一个用于循环访问集合的枚举数,实现了该接口的集合就可以使用foreach遍历了。
关于IEnumerable<T>的所有内容可查看
MSDN:https://msdn.microsoft.com/zh-cn/library/9eekhta0(v=vs.110).aspx
以及源码(微软开放了该接口的源码,这个站点上就是微软C#的部分源代码,吝啬的微软只开放了很少的源码,养成看源代码的习惯是个很强大的技能):http://referencesource.microsoft.com/#mscorlib/system/collections/generic/ienumerable.cs,3acf01620172c7f0
····················································································································································································
ICollection<T>
定义:public interface ICollection<T> : IEnumerable<T>, IEnumerable
属性:
Count |
获取元素总数 |
IsReadOnly |
是否只读 |
方法:
Add() |
添加元素 |
Clear |
清空元素 |
Contains(T) |
确定集合是否包含特定值 |
CopyTo(T [ ],Int32) |
从特定的 ICollection<T> 索引处开始,将 Array 的元素复制到一个 Array 中 |
GetEnumerator() |
返回一个循环访问集合的枚举器。(继承自)IEnumerable<T> |
Remove(T) |
删除 |
关于ICollection<T>的所有内容可查看MSDN:
https://msdn.microsoft.com/zh-cn/library/92t2ye13(v=vs.110).aspx
····················································································································································································
IList<T>
定义:public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
属性:
Item[ Int32 ] |
获取或设置指定索引处的元素。 |
方法:
IndexOf(T) |
确定 IList<T> 中特定项的索引 |
Insert(Int32,T) |
在 IList<T> 中的指定索引处插入一个项 |
RemoveAt(Int32) |
移除位于指定索引处的 IList<T> 项 |
这里只说明了List<T>的部分属性与方法,有一部分属性与方法(继承得到的)并未说明。
关于IList<T>的全部内容可查看MSDN:
https://msdn.microsoft.com/zh-cn/library/5y536ey6(v=vs.110).aspx
····················································································································································································
IComparer<T>
定义:public interface IComparer<in T>
方法:
Compare(T,T) |
比较两个对象并返回指示一个是否小于、 等于还是大于另一个值 |
该接口只有一个方法没有属性,实现了该接口即可使用Sort方法对集合元素进行排序,此接口用于 List<T>.Sort 和List<T>.BinarySearch 方法。 它提供了一种方法自定义集合中的排序顺序。
····················································································································································································
IDictionary<TKey,TValue>
定义:public interface IDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>,IEnumerable<KeyValuePair<TKey, TValue>>,IEnumerable
属性:
Keys |
一个 ICollection<T>对象,它包含实现 IDictionary<TKey, TValue> 的对象的Key |
Values |
一个 ICollection<T>对象,它包含实现 IDictionary<TKey, TValue> 的对象的Value |
方法:
ContainsKey(TKey) |
确定字典中是否包含特定Key的元素 |
TryGetValue(TKey,TValue) |
获取指定Key相关联的Value |
同样也是没说明继承来的属性与方法,关于IDictionary<TKey,TValue>的所有内容可查看MSDN:
https://msdn.microsoft.com/zh-cn/library/s4ys34ea(v=vs.110).aspx
····················································································································································································
常用的最基本的上面已经介绍完毕。这里贴一下关于集合的一些MSDN网址,顺便说一句:多上MSDN,多看源码,尽量看英文原版,翻译好多并不准确。
ISet<T>
MSDN:https://msdn.microsoft.com/zh-cn/library/dd412081(v=vs.110).aspx
IEqualityComparer<T>
MSDN:https://msdn.microsoft.com/zh-cn/library/ms132151(v=vs.110).aspx
System.Collections.Generic
MSDN:https://msdn.microsoft.com/zh-CN/library/system.collections.generic(v=vs.110).aspx
····················································································································································································
三、集合
理解了集合类的接口再来看集合就简单地多了,只要明白某一个集合实现了哪一个接口就能大概明白该集合的用法了。
集合 |
特点与用途 |
List<T> |
List实现了集合的N多接口,通用性最强,具体的功能根据它实现的接口对照上表即可得知。 |
Queue<T> |
先进先出,非常适合用于缓存数据。 |
Statck<T> |
先进后出 |
LinkList<T> |
双向链表,插入与删除很快,搜索很慢。 |
Dictionnary<TKey,TValue> |
搜索很快,根据键值来检索复杂度为O(1)。 |
详细用法(这里就不用太多文字赘述了,没有什么比贴代码更直截了当的说明集合的用法了,再多说一句:看代码能力是一项很强大的技能! ):
1.ArrayList
ArrayList又叫列表,位于System.Collections命名空间。最大的优点就是容量能够动态扩展,声明的时候不需要指定容量。使用方法如下(来自MSDN):
using System; using System.Collections; public class SamplesArrayList { public static void Main() { // Creates and initializes a new ArrayList. ArrayList myAL = new ArrayList(); myAL.Add("Hello"); myAL.Add("World"); myAL.Add("!"); // Displays the properties and values of the ArrayList. Console.WriteLine( "myAL" ); Console.WriteLine( " Count: {0}", myAL.Count ); Console.WriteLine( " Capacity: {0}", myAL.Capacity ); Console.Write( " Values:" ); PrintValues( myAL ); } public static void PrintValues( IEnumerable myList ) { foreach ( Object obj in myList ) Console.Write( " {0}", obj ); Console.WriteLine(); } } /* This code produces output similar to the following: myAL Count: 3 Capacity: 4 Values: Hello World ! */
2.List<T>
ArrayList的泛型等效类,泛型列表。声明的时候指定类型。使用方法(来自MSDN):
using System; using System.Collections.Generic; // Simple business object. A PartId is used to identify the type of part // but the part name can change. public class Part : IEquatable<Part> { public string PartName { get; set; } public int PartId { get; set; } public override string ToString() { return "ID: " + PartId + " Name: " + PartName; } public override bool Equals(object obj) { if (obj == null) return false; Part objAsPart = obj as Part; if (objAsPart == null) return false; else return Equals(objAsPart); } public override int GetHashCode() { return PartId; } public bool Equals(Part other) { if (other == null) return false; return (this.PartId.Equals(other.PartId)); } // Should also override == and != operators. } public class Example { public static void Main() { // Create a list of parts. List<Part> parts = new List<Part>(); // Add parts to the list. parts.Add(new Part() {PartName="crank arm", PartId=1234}); parts.Add(new Part() { PartName = "chain ring", PartId = 1334 }); parts.Add(new Part() { PartName = "regular seat", PartId = 1434 }); parts.Add(new Part() { PartName = "banana seat", PartId = 1444 }); parts.Add(new Part() { PartName = "cassette", PartId = 1534 }); parts.Add(new Part() { PartName = "shift lever", PartId = 1634 }); // Write out the parts in the list. This will call the overridden ToString method // in the Part class. Console.WriteLine(); foreach (Part aPart in parts) { Console.WriteLine(aPart); } // Check the list for part #1734. This calls the IEquitable.Equals method // of the Part class, which checks the PartId for equality. Console.WriteLine("\nContains(\"1734\"): {0}", parts.Contains(new Part {PartId=1734, PartName="" })); // Insert a new item at position 2. Console.WriteLine("\nInsert(2, \"1834\")"); parts.Insert(2, new Part() { PartName = "brake lever", PartId = 1834 }); //Console.WriteLine(); foreach (Part aPart in parts) { Console.WriteLine(aPart); } Console.WriteLine("\nParts[3]: {0}", parts[3]); Console.WriteLine("\nRemove(\"1534\")"); // This will remove part 1534 even though the PartName is different, // because the Equals method only checks PartId for equality. parts.Remove(new Part(){PartId=1534, PartName="cogs"}); Console.WriteLine(); foreach (Part aPart in parts) { Console.WriteLine(aPart); } Console.WriteLine("\nRemoveAt(3)"); // This will remove the part at index 3. parts.RemoveAt(3); Console.WriteLine(); foreach (Part aPart in parts) { Console.WriteLine(aPart); } /* ID: 1234 Name: crank arm ID: 1334 Name: chain ring ID: 1434 Name: regular seat ID: 1444 Name: banana seat ID: 1534 Name: cassette ID: 1634 Name: shift lever Contains("1734"): False Insert(2, "1834") ID: 1234 Name: crank arm ID: 1334 Name: chain ring ID: 1834 Name: brake lever ID: 1434 Name: regular seat ID: 1444 Name: banana seat ID: 1534 Name: cassette ID: 1634 Name: shift lever Parts[3]: ID: 1434 Name: regular seat Remove("1534") ID: 1234 Name: crank arm ID: 1334 Name: chain ring ID: 1834 Name: brake lever ID: 1434 Name: regular seat ID: 1444 Name: banana seat ID: 1634 Name: shift lever RemoveAt(3) ID: 1234 Name: crank arm ID: 1334 Name: chain ring ID: 1834 Name: brake lever ID: 1444 Name: banana seat ID: 1634 Name: shift lever */ } }
3.Queue<T>
队列。用法(来自MSDN):
using System; using System.Collections.Generic; class Example { public static void Main() { Queue<string> numbers = new Queue<string>(); numbers.Enqueue("one"); numbers.Enqueue("two"); numbers.Enqueue("three"); numbers.Enqueue("four"); numbers.Enqueue("five"); // A queue can be enumerated without disturbing its contents. foreach( string number in numbers ) { Console.WriteLine(number); } Console.WriteLine("\nDequeuing '{0}'", numbers.Dequeue()); Console.WriteLine("Peek at next item to dequeue: {0}", numbers.Peek()); Console.WriteLine("Dequeuing '{0}'", numbers.Dequeue()); // Create a copy of the queue, using the ToArray method and the // constructor that accepts an IEnumerable<T>. Queue<string> queueCopy = new Queue<string>(numbers.ToArray()); Console.WriteLine("\nContents of the first copy:"); foreach( string number in queueCopy ) { Console.WriteLine(number); } // Create an array twice the size of the queue and copy the // elements of the queue, starting at the middle of the // array. string[] array2 = new string[numbers.Count * 2]; numbers.CopyTo(array2, numbers.Count); // Create a second queue, using the constructor that accepts an // IEnumerable(Of T). Queue<string> queueCopy2 = new Queue<string>(array2); Console.WriteLine("\nContents of the second copy, with duplicates and nulls:"); foreach( string number in queueCopy2 ) { Console.WriteLine(number); } Console.WriteLine("\nqueueCopy.Contains(\"four\") = {0}", queueCopy.Contains("four")); Console.WriteLine("\nqueueCopy.Clear()"); queueCopy.Clear(); Console.WriteLine("\nqueueCopy.Count = {0}", queueCopy.Count); } } /* This code example produces the following output: one two three four five Dequeuing 'one' Peek at next item to dequeue: two Dequeuing 'two' Contents of the copy: three four five Contents of the second copy, with duplicates and nulls: three four five queueCopy.Contains("four") = True queueCopy.Clear() queueCopy.Count = 0 */
栈。相比于队列,栈是先进后出。定义:
public class Stack<T> : IEnumerable<T>, ICollection, IEnumerable用法(来自MSDN):
using System; using System.Collections.Generic; class Example { public static void Main() { Stack<string> numbers = new Stack<string>(); numbers.Push("one"); numbers.Push("two"); numbers.Push("three"); numbers.Push("four"); numbers.Push("five"); // A stack can be enumerated without disturbing its contents. foreach( string number in numbers ) { Console.WriteLine(number); } Console.WriteLine("\nPopping '{0}'", numbers.Pop()); Console.WriteLine("Peek at next item to destack: {0}", numbers.Peek()); Console.WriteLine("Popping '{0}'", numbers.Pop()); // Create a copy of the stack, using the ToArray method and the // constructor that accepts an IEnumerable<T>. Stack<string> stack2 = new Stack<string>(numbers.ToArray()); Console.WriteLine("\nContents of the first copy:"); foreach( string number in stack2 ) { Console.WriteLine(number); } // Create an array twice the size of the stack and copy the // elements of the stack, starting at the middle of the // array. string[] array2 = new string[numbers.Count * 2]; numbers.CopyTo(array2, numbers.Count); // Create a second stack, using the constructor that accepts an // IEnumerable(Of T). Stack<string> stack3 = new Stack<string>(array2); Console.WriteLine("\nContents of the second copy, with duplicates and nulls:"); foreach( string number in stack3 ) { Console.WriteLine(number); } Console.WriteLine("\nstack2.Contains(\"four\") = {0}", stack2.Contains("four")); Console.WriteLine("\nstack2.Clear()"); stack2.Clear(); Console.WriteLine("\nstack2.Count = {0}", stack2.Count); } } /* This code example produces the following output: five four three two one Popping 'five' Peek at next item to destack: four Popping 'four' Contents of the first copy: one two three Contents of the second copy, with duplicates and nulls: one two three stack2.Contains("four") = False stack2.Clear() stack2.Count = 0 */
5.LinkedList<T>
链表,定义:
public class LinkedList<T> : ICollection<T>, IEnumerable<T>, IEnumerable, ICollection, IReadOnlyCollection<T>, ISerializable, IDeserializationCallback用法:
using System; using System.Text; using System.Collections.Generic; public class Example { public static void Main() { // Create the link list. string[] words = { "the", "fox", "jumped", "over", "the", "dog" }; LinkedList<string> sentence = new LinkedList<string>(words); Display(sentence, "The linked list values:"); Console.WriteLine("sentence.Contains(\"jumped\") = {0}", sentence.Contains("jumped")); // Add the word 'today' to the beginning of the linked list. sentence.AddFirst("today"); Display(sentence, "Test 1: Add 'today' to beginning of the list:"); // Move the first node to be the last node. LinkedListNode<string> mark1 = sentence.First; sentence.RemoveFirst(); sentence.AddLast(mark1); Display(sentence, "Test 2: Move first node to be last node:"); // Change the last node be 'yesterday'. sentence.RemoveLast(); sentence.AddLast("yesterday"); Display(sentence, "Test 3: Change the last node to 'yesterday':"); // Move the last node to be the first node. mark1 = sentence.Last; sentence.RemoveLast(); sentence.AddFirst(mark1); Display(sentence, "Test 4: Move last node to be first node:"); // Indicate, by using parentheisis, the last occurence of 'the'. sentence.RemoveFirst(); LinkedListNode<string> current = sentence.FindLast("the"); IndicateNode(current, "Test 5: Indicate last occurence of 'the':"); // Add 'lazy' and 'old' after 'the' (the LinkedListNode named current). sentence.AddAfter(current, "old"); sentence.AddAfter(current, "lazy"); IndicateNode(current, "Test 6: Add 'lazy' and 'old' after 'the':"); // Indicate 'fox' node. current = sentence.Find("fox"); IndicateNode(current, "Test 7: Indicate the 'fox' node:"); // Add 'quick' and 'brown' before 'fox': sentence.AddBefore(current, "quick"); sentence.AddBefore(current, "brown"); IndicateNode(current, "Test 8: Add 'quick' and 'brown' before 'fox':"); // Keep a reference to the current node, 'fox', // and to the previous node in the list. Indicate the 'dog' node. mark1 = current; LinkedListNode<string> mark2 = current.Previous; current = sentence.Find("dog"); IndicateNode(current, "Test 9: Indicate the 'dog' node:"); // The AddBefore method throws an InvalidOperationException // if you try to add a node that already belongs to a list. Console.WriteLine("Test 10: Throw exception by adding node (fox) already in the list:"); try { sentence.AddBefore(current, mark1); } catch (InvalidOperationException ex) { Console.WriteLine("Exception message: {0}", ex.Message); } Console.WriteLine(); // Remove the node referred to by mark1, and then add it // before the node referred to by current. // Indicate the node referred to by current. sentence.Remove(mark1); sentence.AddBefore(current, mark1); IndicateNode(current, "Test 11: Move a referenced node (fox) before the current node (dog):"); // Remove the node referred to by current. sentence.Remove(current); IndicateNode(current, "Test 12: Remove current node (dog) and attempt to indicate it:"); // Add the node after the node referred to by mark2. sentence.AddAfter(mark2, current); IndicateNode(current, "Test 13: Add node removed in test 11 after a referenced node (brown):"); // The Remove method finds and removes the // first node that that has the specified value. sentence.Remove("old"); Display(sentence, "Test 14: Remove node that has the value 'old':"); // When the linked list is cast to ICollection(Of String), // the Add method adds a node to the end of the list. sentence.RemoveLast(); ICollection<string> icoll = sentence; icoll.Add("rhinoceros"); Display(sentence, "Test 15: Remove last node, cast to ICollection, and add 'rhinoceros':"); Console.WriteLine("Test 16: Copy the list to an array:"); // Create an array with the same number of // elements as the inked list. string[] sArray = new string[sentence.Count]; sentence.CopyTo(sArray, 0); foreach (string s in sArray) { Console.WriteLine(s); } // Release all the nodes. sentence.Clear(); Console.WriteLine(); Console.WriteLine("Test 17: Clear linked list. Contains 'jumped' = {0}", sentence.Contains("jumped")); Console.ReadLine(); } private static void Display(LinkedList<string> words, string test) { Console.WriteLine(test); foreach (string word in words) { Console.Write(word + " "); } Console.WriteLine(); Console.WriteLine(); } private static void IndicateNode(LinkedListNode<string> node, string test) { Console.WriteLine(test); if (node.List == null) { Console.WriteLine("Node '{0}' is not in the list.\n", node.Value); return; } StringBuilder result = new StringBuilder("(" + node.Value + ")"); LinkedListNode<string> nodeP = node.Previous; while (nodeP != null) { result.Insert(0, nodeP.Value + " "); nodeP = nodeP.Previous; } node = node.Next; while (node != null) { result.Append(" " + node.Value); node = node.Next; } Console.WriteLine(result); Console.WriteLine(); } } //This code example produces the following output: // //The linked list values: //the fox jumped over the dog //Test 1: Add 'today' to beginning of the list: //today the fox jumped over the dog //Test 2: Move first node to be last node: //the fox jumped over the dog today //Test 3: Change the last node to 'yesterday': //the fox jumped over the dog yesterday //Test 4: Move last node to be first node: //yesterday the fox jumped over the dog //Test 5: Indicate last occurence of 'the': //the fox jumped over (the) dog //Test 6: Add 'lazy' and 'old' after 'the': //the fox jumped over (the) lazy old dog //Test 7: Indicate the 'fox' node: //the (fox) jumped over the lazy old dog //Test 8: Add 'quick' and 'brown' before 'fox': //the quick brown (fox) jumped over the lazy old dog //Test 9: Indicate the 'dog' node: //the quick brown fox jumped over the lazy old (dog) //Test 10: Throw exception by adding node (fox) already in the list: //Exception message: The LinkedList node belongs a LinkedList. //Test 11: Move a referenced node (fox) before the current node (dog): //the quick brown jumped over the lazy old fox (dog) //Test 12: Remove current node (dog) and attempt to indicate it: //Node 'dog' is not in the list. //Test 13: Add node removed in test 11 after a referenced node (brown): //the quick brown (dog) jumped over the lazy old fox //Test 14: Remove node that has the value 'old': //the quick brown dog jumped over the lazy fox //Test 15: Remove last node, cast to ICollection, and add 'rhinoceros': //the quick brown dog jumped over the lazy rhinoceros //Test 16: Copy the list to an array: //the //quick //brown //dog //jumped //over //the //lazy //rhinoceros //Test 17: Clear linked list. Contains 'jumped' = False //
6.Dictinary<TKey,TVale>
字典,定义:
public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable, IDictionary, ICollection, IReadOnlyDictionary<TKey, TValue>, IReadOnlyCollection<KeyValuePair<TKey, TValue>>, ISerializable, IDeserializationCallback用法(来自MSDN):
using System; using System.Collections.Generic; public class Example { public static void Main() { // Create a new dictionary of strings, with string keys. // Dictionary<string, string> openWith = new Dictionary<string, string>(); // Add some elements to the dictionary. There are no // duplicate keys, but some of the values are duplicates. openWith.Add("txt", "notepad.exe"); openWith.Add("bmp", "paint.exe"); openWith.Add("dib", "paint.exe"); openWith.Add("rtf", "wordpad.exe"); // The Add method throws an exception if the new key is // already in the dictionary. try { openWith.Add("txt", "winword.exe"); } catch (ArgumentException) { Console.WriteLine("An element with Key = \"txt\" already exists."); } // The Item property is another name for the indexer, so you // can omit its name when accessing elements. Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]); // The indexer can be used to change the value associated // with a key. openWith["rtf"] = "winword.exe"; Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]); // If a key does not exist, setting the indexer for that key // adds a new key/value pair. openWith["doc"] = "winword.exe"; // The indexer throws an exception if the requested key is // not in the dictionary. try { Console.WriteLine("For key = \"tif\", value = {0}.", openWith["tif"]); } catch (KeyNotFoundException) { Console.WriteLine("Key = \"tif\" is not found."); } // When a program often has to try keys that turn out not to // be in the dictionary, TryGetValue can be a more efficient // way to retrieve values. string value = ""; if (openWith.TryGetValue("tif", out value)) { Console.WriteLine("For key = \"tif\", value = {0}.", value); } else { Console.WriteLine("Key = \"tif\" is not found."); } // ContainsKey can be used to test keys before inserting // them. if (!openWith.ContainsKey("ht")) { openWith.Add("ht", "hypertrm.exe"); Console.WriteLine("Value added for key = \"ht\": {0}", openWith["ht"]); } // When you use foreach to enumerate dictionary elements, // the elements are retrieved as KeyValuePair objects. Console.WriteLine(); foreach( KeyValuePair<string, string> kvp in openWith ) { Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value); } // To get the values alone, use the Values property. Dictionary<string, string>.ValueCollection valueColl = openWith.Values; // The elements of the ValueCollection are strongly typed // with the type that was specified for dictionary values. Console.WriteLine(); foreach( string s in valueColl ) { Console.WriteLine("Value = {0}", s); } // To get the keys alone, use the Keys property. Dictionary<string, string>.KeyCollection keyColl = openWith.Keys; // The elements of the KeyCollection are strongly typed // with the type that was specified for dictionary keys. Console.WriteLine(); foreach( string s in keyColl ) { Console.WriteLine("Key = {0}", s); } // Use the Remove method to remove a key/value pair. Console.WriteLine("\nRemove(\"doc\")"); openWith.Remove("doc"); if (!openWith.ContainsKey("doc")) { Console.WriteLine("Key \"doc\" is not found."); } } } /* This code example produces the following output: An element with Key = "txt" already exists. For key = "rtf", value = wordpad.exe. For key = "rtf", value = winword.exe. Key = "tif" is not found. Key = "tif" is not found. Value added for key = "ht": hypertrm.exe Key = txt, Value = notepad.exe Key = bmp, Value = paint.exe Key = dib, Value = paint.exe Key = rtf, Value = winword.exe Key = doc, Value = winword.exe Key = ht, Value = hypertrm.exe Value = notepad.exe Value = paint.exe Value = paint.exe Value = winword.exe Value = winword.exe Value = hypertrm.exe Key = txt Key = bmp Key = dib Key = rtf Key = doc Key = ht Remove("doc") Key "doc" is not found. */
最后在说一点,以上各个集合的性能比较: