什么时候该用 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 起就稳定保持插入顺序,这对调试和日志可重现性至关重要。
