C# 哈希表Hashtable使用方法 C# Hashtable和Dictionary的区别

来源:这里教程网 时间:2026-02-21 17:40:55 作者:

什么时候该用
Hashtable
,而不是
Dictionary

绝大多数新项目里,

Hashtable
已不是首选。它只在两类场景下仍有实际价值:
一是维护非常老的 .NET Framework 1.x/2.0 代码,且升级成本过高;二是需要运行时动态混存多种键类型(比如同一集合里同时存
int
键、
string
键、
Guid
键),且明确接受类型不安全和性能损耗。

Hashtable
允许
hashtable.Add(1, "a")
hashtable.Add("key", DateTime.Now)
混用,
Dictionary
编译直接报错
但代价是:每次取值都要强制转型,比如
(string)hashtable[1]
,一旦类型写错,程序在运行时才崩
.NET Core / .NET 5+ 中
Hashtable
已被标记为“遗留类型”,官方文档明确建议迁移到
Dictionary
或线程安全的
ConcurrentDictionary

Hashtable
的基本用法和典型陷阱

初始化后,所有键值都以

object
存储,遍历时必须用
DictionaryEntry
,不能直接解构:

Hashtable ht = new Hashtable();
ht.Add("name", "Alice");
ht.Add(42, true);
// ✅ 正确遍历方式
foreach (DictionaryEntry entry in ht)
{
    Console.WriteLine($"{entry.Key} → {entry.Value}");
}
// ❌ 错误:不能直接 foreach (var kv in ht)
// ❌ 错误:ht["missing"] 返回 null,不是抛异常 —— 容易掩盖逻辑错误
访问不存在的键(如
ht["xxx"]
)返回
null
,不会抛
KeyNotFoundException
,容易漏掉空引用异常
ContainsKey
ContainsValue
都是 O(n) 查找(因为 value 未索引),大数据量时很慢
键为
null
是允许的(仅一个),但值为
null
完全没问题 —— 这和
Dictionary
对 null 键的严格限制完全不同

性能差异到底差在哪?不只是“泛型 vs 非泛型”

关键瓶颈在值类型操作:

int
bool
DateTime
等作为键或值时,
Hashtable
必然触发装箱(boxing),而
Dictionary<int string></int>
完全避免。

实测:10 万次插入
int→string
Dictionary
Hashtable
快 2–3 倍(.NET 6 环境)
Hashtable
内部每个节点是
DictionaryEntry
对象,堆分配频繁,GC 压力大;
Dictionary
用紧凑数组 + 索引管理,缓存友好
哈希冲突处理上,
Dictionary
在 .NET Core 2.1+ 中链表长度 >8 自动转红黑树,最坏查找从 O(n) 降到 O(log n);
Hashtable
始终是链表,无此优化

线程安全别被“文档误导”

文档说

Hashtable.Synchronized()
是线程安全的,但实际是“伪安全”——它只保证单个方法原子性(如单次
Add
不会中断),不保证多步操作的事务性。例如“检查是否存在再添加”这种常见模式依然会出错:

var syncHt = Hashtable.Synchronized(new Hashtable());
if (!syncHt.ContainsKey("x")) {
    syncHt.Add("x", "val"); // ❌ 仍可能并发重复添加!
Dictionary
默认完全不加锁,多线程写必须自己用
lock
包裹整段逻辑,性能损失明显
真要线程安全,应直接用
ConcurrentDictionary<tkey tvalue></tkey>
,它针对读多写少做了精细分段锁,比包装后的
Hashtable
实际吞吐更高
注意:
Hashtable
的“线程安全”仅限于 .NET Framework;.NET Core 中
Synchronized()
方法已移除,调用即抛
PlatformNotSupportedException

真正容易被忽略的是:

Hashtable
的无序性不是“偶尔乱”,而是完全不可预测——哪怕插入顺序固定,不同 .NET 版本、不同 CPU 架构下遍历结果都可能不同;而
Dictionary
自 .NET Framework 3.5 起就稳定保持插入顺序,这对调试和日志可重现性至关重要。

相关推荐