有序列表中还有一种叫类字典的集合。类字典的集合是通过存储键/值的列表去,然后基于键去查询值。
这课中你将学到:
■ 使用Hashtable去创建一个简单的不同数据项的列表。
■ 使用SortedList去给对象列表排序。
■ 使用DictionaryEntry 去存储名/值对。
■ 枚举字典并且知道如何使用DictionaryEntries.
■ 理解IEqualityComparison 接口去提供Hashtables内集合项的独一无二性。
■ 以一种非常有效率的方式使用HybridDictionary 去存储名/值对 。
■ 使用OrderedDictionary去存储名/值对,并且能维持集合内的集合项的顺序。
使用 Dictionary
Dictionary类用一个键去定位一个值。特别地,它们的存在允许你去创建查询表,它能用任意的键去映射任意的值。在大部分的情况下,Hashtable类被用于做键/值对的映射。如:你需要把e-mail地址映射到用户名,你需要使用Hashtable 去存储这个映射,如下:
Hashtable emailLookup = new Hashtable();
// Add method takes a key (first parameter)
// and a value (second parameter)
emailLookup.Add("sbishop@contoso.com", "Bishop, Scott");
// The indexer is functionally equivalent to Add
emailLookup["sbishop@contoso.com"] = "Bishop, Scott";
不像以前的集合类型,类字典的集合总是要把信息的两部分存入集合中:有两种方式去加集合项:可以使用Add方法通过定义键/值对去加集合项。也可以用索引器去定义键/值对。
从字典中获取对象也很简单。你可以简单地通过索引器去访问:
Console.WriteLine(emailLookup["sbishop@contoso.com"]);
因为类字典的集合是为了查询键/值的,所以直接通过类字典的集合内的对象去迭代集合项是不可能的。比如,你要迭代Hashtable内所有的value,如下:
Hashtable emailLookup = new Hashtable();
emailLookup["sbishop@contoso.com"] = "Bishop, Scott";
emailLookup["chess@contoso.com"] = "Hess, Christian";
emailLookup["djump@contoso.com"] = "Jump, Dan";
foreach (object name in emailLookup)
{
Console.WriteLine(name);
}
你可能期望显示变量emailLookup中每个人的人名。 结果显示如下:
System.Collections.DictionaryEntry
System.Collections.DictionaryEntry
System.Collections.DictionaryEntry
为什么会这样?因为你实际上迭代的是Dictionary对象中的条目,而不是键或值。如果你想要迭代出所有人名,你应该改变迭代器,使之使用DictionaryEntry对象,如:
foreach (DictionaryEntry entry in emailLookup)
{
Console.WriteLine(entry.Value);
}
一个 DictionaryEntry 对象可以简单的看作一个包含一个容器,包含着一个键和一个值。所以得到值与迭代一样简单,但是你必须获取你所需要的值或键。所有的字典类(包括Hashtable)都实现了IDictionary 接口. IDictionary 接口继承ICollection 接口。IDictionary 接口最重要的属性和方法如下:
Table 4-13 IDictionary 属性
Name Description
IsFixedSize 返回这个集合是否是大小可变的。
IsReadOnly 返回这个集合是否是只读的。
Item 得到或设置集合中特定的项
Keys 返回一个ICollection 对象,包含集合中键的列表。
Values 返回一个ICollection 对象,包含集合中值的列表。
Table 4-14 IDictionary 方法
Name Description
Add 加一个键/值对到集合中。
Clear 清除集合中所有的项。
Contains 测试一个特定的键是否存在于集合中。
GetEnumerator 为这个集合返回一个IDictionaryEnumerator 对象。这个方法不 1 1 同于IEnumerable接口中返回IEnumerator的同名方法
Remove 删除集合内一个特定的项
IDictionary 接口有些像IList 接口,但是IDictionary接口不允许通过索引访问,只允许通过键来访问。这个接口使得你可直接访问集合的键和值。这种设计使你可分别迭代键和值。你既可以通过迭代DictionaryEntry 对象访问键和值。你也可以通过迭代Value 属性如下:
foreach (object name in emailLookup.Values)
{
Console.WriteLine(name);
}
除了实现IDictionary 接口, the Hashtable还支持两个方法去测试键和值是否存在。如下:
Table 4-15 Hashtable 方法
Name Description
ContainsKey 判断集合是否包含一个特定的键。
ContainsValue 判断集合是否包含一个特定的值。
理解相等
Hashtable 类是一个字典类的具体类型,它使用一个整形值(被称作一个hash)去协助键的存储。.NET中的每个对象对继承Object类。Object类实现GetHash方法,它返回了一个整形值,它和这个对象是唯一对应的。为什么Hashtable类要为程序员存一个hash值呢?Hashtable仅仅允许值的hash是独一无二的,而不是独一无二的值.如果你存贮相同的键,第二次符的值会覆盖第一次符的值,如下:
Hashtable duplicates = new Hashtable();
duplicates["First"] = "1st";
duplicates["First"] = "the first";
Console.WriteLine(duplicates.Count); // 1
在这个例子中,集合duplicates 内仅仅只有一个集合项,因为First 的hash值是相同的。String类覆盖了Object的GetHashCode方法。String的GetHashCode方法认为只要字符串的文本相同就相同,即使它们是不同的实例。这就是Hashtable类通过测试对象的hash码去判断是否相对。有时候.NET Framework 并不能总是理解我们的意思。例如,你创建了一个称作Fish 的简单的类,并有一个fish成员。如:
public class Fish
{
string name;
public Fish(string theName)
{
name = theName;
}
}
现在如果你用相同的成员去创造两个实例,Hashtable 会以不同的对象对待,如:
Hashtable duplicates = new Hashtable();
Fish key1 = new Fish("Herring");
Fish key2 = new Fish("Herring");
duplicates[key1] = "Hello";
duplicates[key2] = "Hello";
Console.WriteLine(duplicates.Count); // 2
为什么有相同成员的项被认为是两个不同的项呢?在这个例子中duplicates 集合有两个数据项的原因是,类对象对GetHashCode 方法的实现是对每个类的实例创造一个不同的hash码。你能覆盖Fish 类中的GetHashCode 方法,然后让Hashtable 认为它们相等。如:
public override int GetHashCode()
{
return name.GetHashCode();
}
如果你返回类fish 的成员的hash码, 这两实例将会有相同的hash码. 但是Hashtable 能判断这是两个相同的对象吗?不行的是,不能。如果Hashtable发现两个对象有相同的hash值,它会调用Equals方法去看这两个对象是否真的相同。而在相同的类的不同实例的情况下,Object.Equals方法默认会返回false。所以我们需要为Fish类覆盖Equals方法:
public override bool Equals(object obj)
{
Fish otherFish = obj as Fish;
if (otherFish == null) return false;
return otherFish.name == name;
}
这样我们能够通过判断成员的名称的Hash是否相等,然后再判断两个对象是否真的相等。从而判断两个键是否相等。
使用IEqualityComparer 接口
除了改变你的类以提供判等的方法,你可能会发现在类外提供判等也是必须的。如:假如你想把字符串作为Hashtable 的键值,但要忽略字符串的大小写。改变String类去支持这个需要或创建你自己的实现大小写忽略的String类是件痛苦的事。这个解决方案是Hashtable能提供一个类去处理相等性。Hashtable类提供一个构造器去接受一个IEqualityComparer类的实例作为参数。像IComparer一样去允许你在集合内排序,IEqualityComparer接口提供两个方法:GetHashCode 和 Equals. 这两个方法允许比较类去处理对象的判等而不依赖提供给它们的对象。如:以下创建了一个简单的字符串大小写不敏感的比较器:
public class InsensitiveComparer : IEqualityComparer
{
CaseInsensitiveComparer _comparer = new CaseInsensitiveComparer();
public int GetHashCode(object obj)
{
return obj.ToString().ToLowerInvariant().GetHashCode();
}
public new bool Equals(object x, object y)
{
if (_comparer.Compare(x, y) == 0)
{
return true;
}
else
{
return false;
}
}
}
在这个新类中,你实现了IEqualityComparer 接口去提供hash码和判等比较。注意这个类使用内建的CaseInsensitiveComparer去做实际上的判等比较。还有,GetHash
Hashtable dehash = new Hashtable(new InsensitiveComparer());
dehash["First"] = "1st";
dehash["Second"] = "2nd";
dehash["Third"] = "3rd";
dehash["Fourth"] = "4th";
dehash["fourth"] = "4th";
Console.WriteLine(dehash.Count); // 4
因为你在创建Hashtable 类时,用的是大小写不敏感的判等对象,所以“Fourth” 和 “fourth”被认为是相同的。当我们要创建一个查询表时,Hashtable类是一个很有用的类,除非你要通过某些键值去给集合项排序。当你迭代Hashtable类时,它以hash值的顺序去迭代。大部分情况下,这个顺序不是实际的。SortedList是一个支持键排序的字典。
使用SortedList类
虽然SortedList类是一个字典类,它也拥有简单列表的一些功能。这意味着你能顺序地访问存在SortedList中的集合项。例如:你使用一个SortedList去给一个简单列表排序:
SortedList sort = new SortedList();
sort["First"] = "1st";
sort["Second"] = "2nd";
sort["Third"] = "3rd";
sort["Fourth"] = "4th";
sort["fourth"] = "4th";
foreach (DictionaryEntry entry in sort)
{
Console.WriteLine("{0} = {1}", entry.Key, entry.Value);
}
This code results in a simple sorting of our objects:
First = 1st
fourth = 4th
Fourth = 4th
Second = 2nd
Third = 3rd
它仍然是个字典类,从DictionaryEntry可以看出。除了所有字典类实现的接口,SortedList类也支持别的属性允许通过索引号去访问键和值。
Table 4-16 SortedList 属性
Name Description
Capacity 得到和设置当前集合内所能容纳集合项的个数。
(Count 是集合内的集合项的个数)
Table 4-17 SortedList 方法
Name Description
ContainsKey 确定是否包含特定的键。
ContainsValue 确定是否包含特定的值。
GetByIndex 通过特定的键获取集合中的值
GetKey 以一个特定的索引获取集合中的键。
GetKeyList 获取一个键的有序列表。
GetValueList 获取值的列表。
IndexOfKey 获得集合内的键的索引。
IndexOfValue 获取集合内第一个符合特定值的索引。
RemoveAt 用索引删除特定值。
SetByIndex 在集合中的一个特定索引上赋值。
TrimToSize 释放不用的集合空间。
由这几个表所示,SortedList 加入了几个方法可以通过索引号去访问数据。这个类支持通过索引获取键和值,并且它也支持通过键和值去查询它们的索引。因为这个类是有序的,随着集合项的添加和删除集合项的索引会改变。所以,你能够在你创建SortedList时,实现一个IComparer以至于你能控制排序的方法。如果你借用DescendingComparer 类,你能够去改变排序:
SortedList sort = new SortedList(new DescendingComparer());
sort["First"] = "1st";
sort["Second"] = "2nd";
sort["Third"] = "3rd";
sort["Fourth"] = "4th";
foreach (DictionaryEntry entry in sort)
{
Console.WriteLine("{0} = {1}", entry.Key, entry.Value);
}
现在的排序是降序了(记住顺序是按名称的字母排序)
Third = 3rd
Second = 2nd
Fourth = 4th
First = 1st
特殊的字典
有些时候标准的字典(SortedList和Hashtable)有限制,可能是功能上的限制或和性能上相关的限制。为了克服这些缺点,.NET Framework 支持三个另外的字典: ListDictionary, HybridDictionary,和 OrderedDictionary.
ListDictionary
一般而言,Hashtable 是一个非常有效率的集合。但Hashtable的一个问题是它要求一些开支,并且对于小的集合(少于是个元素的集合)这个开支能降低性能。ListDictionary能够弥补这个缺陷。它的实现像是一个简单的数组,所以它对于小的集合是非常有效的。ListDictionary 类实现的接口Hashtable 都实现了。如:
ListDictionary emailLookup = new ListDictionary ();
emailLookup["sbishop@contoso.com"] = "Bishop, Scott";
emailLookup["chess@contoso.com"] = "Hess, Christian";
emailLookup["djump@contoso.com"] = "Jump, Dan";
foreach (DictionaryEntry entry in emailLookup)
{
Console.WriteLine(entry.Value);
}
HybridDictionary
如你所见,对于小的集合Hashtable是没有效率的。但是对于较大的列表ListDictionary也是没有效率的。总体的说,当集合小时,我们用ListDictionary。当集合大时,我们用Hashtable。如果我们不知道集合的大小呢?HybridDictionary来弥补这个缺陷。HybridDictionary是作为一个ListDictionary来实现的,但当集合过大时,它会在内部把自己转化为Hashtable。HybridDictionary最好用在有一些列表,其中一些很小,另一些却很大。ListDictionary 类实现的接口Hashtable 都实现了。所以Hashtable可以代替ListDictionary,如下:
HybridDictionary emailLookup = new HybridDictionary ();
emailLookup["sbishop@contoso.com"] = "Bishop, Scott";
emailLookup["chess@contoso.com"] = "Hess, Christian";
emailLookup["djump@contoso.com"] = "Jump, Dan";
foreach (DictionaryEntry entry in emailLookup)
{
Console.WriteLine(entry.Value);
}
OrderedDictionary
当你想要Hashtable 的功能但同时又想控制集合项的次序。当你把一项放入Hashtable 中,有两件事可以肯定:无法通过索引去访问;并且如果你使用枚举器去访问所有的,集合中所有的项会根据hash码去排序.你可能会去使用SortedList,但是你要假定键是按你需要的排序方法。为了满足一个快速的字典的需要同时还要有序,.NET Framework支持OrderedDictionary来同时满足这两个需求。OrderedDictionary很像Hashtable,只是它附加了自己的属性和方法。
Table 4-18 OrderedDictionary 增加的属性
Name Description
Item 重载支持通过索引访问。
Table 4-19 OrderedDictionary 增加的方法
Name Description
Insert 通过索引号在集合内插入键/值对。
RemoveAt 通过索引号删除集合内的一个键/值对。
这个类附加的这几个允许它处理集合如同这个类是ArrayList 和Hashtable 的混合体。