表示键/值对的集合,这些键/值对根据键的哈希代码进行组织。
命名空间:System.Collections
备注
每个元素都是一个存储在 DictionaryEntry
对象中的键/值对。键不能为 空引用(在 Visual Basic 中为 Nothing),但值可以。
要重写 Object.GetHashCode
方法(或 IHashCodeProvider
接口)和 Object.Equals
方法(或 IComparer
接口),需要有被 Hashtable
用作键的对象。方法和接口的实现必须以相同的方式处理大小写;否则,Hashtable
的行为可能不正确。例如,创建 Hashtable
时,您必须配合使用 CaseInsensitiveHashCodeProvider
类(或任何不区分大小写的 IHashCodeProvider
实现)和 CaseInsensitiveComparer
类(或任何不区分大小写的 IComparer
实现)。
此外,如果该键存在于 Hashtable
中,那么当使用相同参数调用这些方法时,这些方法必须生成相同的结果。还有一种方式是使用具有 IEqualityComparer
参数的 Hashtable
构造函数。如果键相等性只是引用相等性,则 Object.GetHashCode
和 Object.Equals
的继承实现将满足需要。
只要键对象用作 Hashtable
中的键,它们就必须是永远不变的。
当把某个元素添加到 Hashtable
时,将根据键的哈希代码将该元素放入存储桶中。该键的后续查找将使用键的哈希代码只在一个特定存储桶中搜索,这将大大减少为查找一个元素所需的键比较的次数。
Hashtable
的加载因子确定元素与存储桶的最大比率。加载因子越小,平均查找速度越快,但消耗的内存也增加。默认的加载因子 1.0 通常提供速度和大小之间的最佳平衡。当创建 Hashtable
时,也可以指定其他加载因子。
当向 Hashtable
添加元素时,Hashtable
的实际加载因子将增加。当实际加载因子达到指定的加载因子时,Hashtable
中存储桶的数目自动增加到大于当前 Hashtable
存储桶数两倍的最小质数。
Hashtable
中的每个键对象必须提供其自己的哈希函数,可通过调用 GetHash
访问该函数。但是,可将任何实现 IHashCodeProvider
的对象传递到 Hashtable
构造函数,而且该哈希函数用于该表中的所有对象。
Hashtable 的容量是 Hashtable 可拥有的元素数。Hashtable 的默认初始容量为零。随着向 Hashtable 中添加元素,容量通过重新分配按需自动增加。
C# 语言中的 foreach
语句(在 Visual Basic 中为 for each)需要集合中每个元素的类型。由于 Hashtable
的每个元素都是一个键/值对,因此元素类型既不是键的类型,也不是值的类型。而是 DictionaryEntry
类型。例如:
foreach (DictionaryEntry de in myHashtable) {...}
foreach
语句是对枚举数的包装,它只允许从集合中读取,不允许写入集合。
因为序列化和反序列化 Hashtable
的枚举数会使元素重新排序,所以不调用 Reset
方法就不能继续枚举。
示例
下面的示例说明如何对 Hashtable 创建、初始化并执行各种函数以及如何打印出其键和值。
using System;
using System.Collections;
class Example
{
public static void Main()
{
//创建一个新的哈希表
//
Hashtable openWith = new Hashtable();
//将一些元素添加到散列表中。 没有重复的键,但其中一些值是重复的。
openWith.Add("txt", "notepad.exe");
openWith.Add("bmp", "paint.exe");
openWith.Add("dib", "paint.exe");
openWith.Add("rtf", "wordpad.exe");
// 如果新密钥已存在于散列表中,则Add方法将引发异常。
try
{
openWith.Add("txt", "winword.exe");
}
catch
{
Console.WriteLine("An element with Key = \"txt\" already exists.");
}
//Item属性是默认属性,所以在访问元素时可以省略它的名字。
Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]);
//默认的Item属性可以用来改变与某个键相关的值。
openWith["rtf"] = "winword.exe";
Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]);
//如果某个键不存在,则为该键设置默认的Item属性会添加一个新的键/值对。
openWith["doc"] = "winword.exe";
//如果请求的key不在散列表中,默认的Item属性会引发异常。
try
{
Console.WriteLine("For key = \"tif\", value = {0}.", openWith["tif"]);
}
catch
{
Console.WriteLine("Key = \"tif\" is not found.");
}
// 在插入它们之前,可以使用ContainsKey来测试Key。
if (!openWith.ContainsKey("ht"))
{
openWith.Add("ht", "hypertrm.exe");
Console.WriteLine("Value added for key = \"ht\": {0}", openWith["ht"]);
}
//当您使用foreach枚举散列表元素时,元素将被检索为KeyValuePair对象。
Console.WriteLine();
foreach( DictionaryEntry de in openWith )
{
Console.WriteLine("Key = {0}, Value = {1}", de.Key, de.Value);
}
//要单独获取值,请使用Values属性。
ICollection valueColl = openWith.Values;
//ValueCollection的元素使用为散列表值指定的类型进行强类型化。
Console.WriteLine();
foreach( string s in valueColl )
{
Console.WriteLine("Value = {0}", s);
}
//要单独获取keys,请使用Keys属性。
ICollection keyColl = openWith.Keys;
// KeyCollection的元素使用为散列表键指定的类型强类型化。
Console.WriteLine();
foreach( string s in keyColl )
{
Console.WriteLine("Key = {0}", s);
}
//使用Remove方法删除一个键/值对。
Console.WriteLine("\nRemove(\"doc\")");
openWith.Remove("doc");
if (!openWith.ContainsKey("doc"))
{
Console.WriteLine("Key \"doc\" is not found.");
}
}
}
此代码示例生成以下输出:
/*
An element with Key = "txt" already exists.
For key = "rtf", value = wordpad.exe.
For key = "rtf", value = winword.exe.
For key = "tif", value = .
Value added for key = "ht": hypertrm.exe
Key = dib, Value = paint.exe
Key = txt, Value = notepad.exe
Key = ht, Value = hypertrm.exe
Key = bmp, Value = paint.exe
Key = rtf, Value = winword.exe
Key = doc, Value = winword.exe
Value = paint.exe
Value = notepad.exe
Value = hypertrm.exe
Value = paint.exe
Value = winword.exe
Value = winword.exe
Key = dib
Key = txt
Key = ht
Key = bmp
Key = rtf
Key = doc
Remove("doc")
Key "doc" is not found.
*/
线程安全
Hashtable
是线程安全的,可由多个读取器线程或一个写入线程使用。多线程使用时,如果任何一个线程执行写入(更新)操作,它都不是线程安全的。若要支持多个编写器,如果没有任何线程在读取 Hashtable
对象,则对 Hashtable
的所有操作都必须通过 Synchronized
方法返回的包装完成。
从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合,或者捕捉由于其他线程进行的更改而引发的异常。
Hashtable.Synchronized 方法
返回 Hashtable 的同步(线程安全)包装。
备注
如果没有任何线程在读取 Hashtable
,则 Synchronized
支持使用多个写入线程。如果使用一个或多个读取器以及一个或多个编写器,则同步包装不提供线程安全的访问。
从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合,或者捕捉由于其他线程进行的更改而引发的异常。
下面的代码示例显示如何在整个枚举过程中使用 SyncRoot
锁定集合:
Hashtable myCollection = new Hashtable();
lock(myCollection.SyncRoot) {
foreach (Object item in myCollection) {
// 在这里插入你的代码
}
}
此方法是 O(1) 操作。
示例
下面的示例演示如何同步 Hashtable
、如何确定 Hashtable
是否同步以及如何使用同步的 Hashtable
。
using System;
using System.Collections;
public class SamplesHashtable {
public static void Main() {
// 创建并初始化一个新的Hashtable。
Hashtable myHT = new Hashtable();
myHT.Add( 0, "zero" );
myHT.Add( 1, "one" );
myHT.Add( 2, "two" );
myHT.Add( 3, "three" );
myHT.Add( 4, "four" );
//在Hashtable周围创建一个同步包装。
Hashtable mySyncdHT = Hashtable.Synchronized( myHT );
// 显示两个哈希表的同步状态。
Console.WriteLine( "myHT is {0}.", myHT.IsSynchronized ? "synchronized" : "not synchronized" );
Console.WriteLine( "mySyncdHT is {0}.", mySyncdHT.IsSynchronized ? "synchronized" : "not synchronized" );
}
}
该代码产生以下输出
/*
myHT is not synchronized.
mySyncdHT is synchronized.
*/