Hashtable的定义
表示键/值对的集合,这些键/值对根据键的哈希代码进行组织。
Hashtable存储结构如下
Hashtable是非泛型的集合,所以在检索和存储值类型时通常会发生装箱与拆箱的操作。
当把某个元素添加到 Hashtable 时,将根据键的哈希代码将该元素放入存储桶中,由于是散列算法所以会出现一个哈希函数能够为两个不同的键生成相同的哈希代码,该键的后续查找将使用键的哈希代码只在一个特定存储桶中搜索,这将大大减少为查找一个元素所需的键比较的次数。
Hashtable 的加载因子确定元素与Hashtable 可拥有的元素数的最大比率。加载因子越小,平均查找速度越快,但消耗的内存也增加。默认的加载因子 0.72通常提供速度和大小之间的最佳平衡。当创建 Hashtable 时,也可以指定其他加载因子。
元素总量/ Hashtable 可拥有的元素数=加载因子
当向 Hashtable 添加元素时,Hashtable 的实际加载因子将增加。当实际加载因子达到指定的加载因子时,Hashtable 中存储桶的数目自动增加到大于当前 Hashtable 存储桶数两倍的最小素数。
扩容时所有的数据需要重新进行散列计算。虽然Hash具有O(1)的数据检索效率,但它空间开销却通常很大,是以空间换取时间。所以Hashtable适用于读取操作频繁,写入操作很少的操作类型。
代码一、
static void Main(string[] args)
{
Hashtable hashtb = new Hashtable();
hashtb.Add(1, "aa");
hashtb.Add(2, "bb");
hashtb.Add(3, "cc");
hashtb.Add(4, "dd");
foreach (DictionaryEntry item in hashtb)
{
Console.WriteLine(item.Value);
item.Value = "ee";
}
Console.Read();
}
编译出错:item为foreach的迭代变量,无法修改其成员。
原因:如果运行foreach处理语句试图修改迭代变量值,或将变量值作为ref参数或out参数传递,那么都会发生编译错误,迭代变量相当于一个局部只读变量。
代码二、
item.Value = "ee";改成hashtb[item.Key] = "ee";
运行报错:集合已修改;可能无法执行枚举操作。
原因:.NET Framework 提供枚举数作为循环访问一个集合的简单方法。枚举数只读取集合中的数据,无法用于修改基础集合。
foreach 语句用于循环访问集合,以获取您需要的信息,但不能用于在源集合中添加或移除项,否则可能产生不可预知的副作用。如果需要在源集合中添加或移除项,请使用 for 循环。
代码三、
Thread tr1 = new Thread(new ThreadStart(() =>
{
foreach (DictionaryEntry item in hashtb)
{
Console.WriteLine(item.Value);
}
}));
tr1.Start();
Thread tr2 = new Thread(new ThreadStart(() =>
{
for (int i = 1; i < 4; i++)
{
hashtb[i] = "ee";
Console.WriteLine(hashtb[i]);
}
}));
tr2.Start();
线程tr1用来读hashtable,线程tr2用来foreach来枚举修改hashtable,
运行时错误:集合已修改;可能无法执行枚举操作。
代码四、
//读取
Thread tr1 = new Thread(new ThreadStart(() =>
{
for (int i = 1; i <= 4; i++)
{
Console.WriteLine(hashtb[i]);
}
}));
tr1.Start();
Thread tr2 = new Thread(new ThreadStart(() =>
{
for (int i = 1; i <= 4; i++)
{
Console.WriteLine(hashtb[i]);
}
}));
tr2.Start();
//修改
Thread tr3 = new Thread(new ThreadStart(() =>
{
for (int i = 1; i <= 4; i++)
{
hashtb[i] = "ee";
}
}));
tr3.Start();
运行结果:正常
说明:Hashtable 是线程安全的,可由多个读取器线程和一个写入线程使用。多线程使用时,如果只有一个线程执行写入(更新)操作,则它是线程安全的,从而允许进行无锁定的读取(若编写器序列化为 Hashtable)
代码五、
Thread tr1 = new Thread(new ThreadStart(() =>
{
lock (hashtb)
{
foreach (DictionaryEntry item in hashtb)
{
Console.WriteLine(item.Value);
}
}
}));
tr1.Start();
Thread tr2 = new Thread(new ThreadStart(() =>
{
lock (hashtb)
{
for (int i = 1; i <= 4; i++)
{
hashtb[i] = "ee";
}
}
}));
tr2.Start();
运行结果:正常
说明:由于两个线程里面都加了lock (hashtb)所以是线程安全的。 从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合,或者捕捉由于其他线程进行的更改而引发的异常。
Hashtable 提供的线程安全方法
Hashtable的Synchronized静态方法提供线程安全的实例,如下:
Hashtable ht = Hashtable.Synchronized(new Hashtable());
内部实现如下:
public override void Add(object key, object value)
{
lock (this._table.SyncRoot)
{
this._table.Add(key, value);
}
}
按输入方式输出
因为hashtable内部是无序的,所以输出不一定,hashtable取数据的机制没搞明白。按照下面代码可以实现先进先出。
可以通过控制ArrayList里面keys的排序来控制hashtable的输出,当然也可以用SortedDictionary和SortedList实现排序集合。
public class NoSortHashtable : Hashtable
{
private ArrayList keys = new ArrayList();
public NoSortHashtable()
{
}
public override void Add(object key, object value)
{
base.Add (key, value);
keys.Add (key);
}
public override ICollection Keys
{
get
{
return keys;
}
}
public override void Clear()
{
base.Clear ();
keys.Clear ();
}
public override void Remove(object key)
{
base.Remove (key);
keys.Remove (key);
}
public override IDictionaryEnumerator GetEnumerator()
{
return base.GetEnumerator ();
}
}
public class NoSortHashtable : Hashtable
{
private ArrayList keys = new ArrayList();
public NoSortHashtable()
{
}
public override void Add(object key, object value)
{
base.Add (key, value);
keys.Add (key);
}
public override ICollection Keys
{
get
{
return keys;
}
}
public override void Clear()
{
base.Clear ();
keys.Clear ();
}
public override void Remove(object key)
{
base.Remove (key);
keys.Remove (key);
}
public override IDictionaryEnumerator GetEnumerator()
{
return base.GetEnumerator ();
}
}