Dictionary vs Hashtable:C#中的集合对决
\n\n想象一下这个场景:你正在用C#编写代码,需要根据键快速存储和检索数据。你打开文档,看到两个相似的类——Dictionary和Hashtable。两者都基于“键-值”原则工作,都承诺提供闪电般的数据访问速度。但该选哪一个呢?
如果你曾为此困惑,你并不孤单。在Stack Overflow上,“为什么在C#中Dictionary比Hashtable更受青睐?”这个话题获得了数千次浏览和数百次投票。今天,我们将一劳永逸地解答这个问题。剧透:Dictionary几乎总是胜出。但让我们来探讨一下原因。
\n\n历史回顾:Hashtable从何而来?
\n\n要理解这场对决的本质,我们需要回顾C#的历史。当.NET平台首次出现(版本1.0)时,开发者可以使用来自System.Collections命名空间的Hashtable类。这是一个经典的哈希表,可以存储任何对象。听起来很酷,对吧?但有一个问题——缺乏类型化。
随着.NET 2.0的发布以及泛型(generics)的出现,微软引入了一个新的集合——来自System.Collections.Generic命名空间的Dictionary<TKey, TValue>。真正的魔法就此开始。
类型安全:Dictionary的主要优势
\n\n让我们看一个具体的代码示例。以下是使用Hashtable的方式:
// 旧方法:Hashtable\nHashtable hashtable = new Hashtable();\nhashtable.Add("键1", 123);\nhashtable.Add("键2", "字符串"); // 错误?不,可以编译通过!\nhashtable.Add(456, "另一个字符串"); // 键也可以是任何对象\n\n// 现在尝试获取数据\nint value = (int)hashtable["键2"]; // 哎呀!运行时抛出InvalidCastException异常\n\n看到问题了吗?Hashtable接受任何类型——无论是键还是值。编译器会放过任何错误,你只有在程序运行时才会发现。这直接导致了难以捕获的bug。
\n\n现在来看看Dictionary:
\n\n// 新的、正确的方法:Dictionary\nDictionary<string, int> dictionary = new Dictionary<string, int>();\ndictionary.Add("键1", 123);\n// dictionary.Add("键2", "字符串"); // 编译错误!类型不匹配\n// dictionary.Add(456, "另一个字符串"); // 也是错误——键必须是string类型\n\n// 无需强制转换即可检索数据\nint value = dictionary["键1"]; // 干净、安全、优雅\n\nDictionary是类型安全的。你明确指定了键和值的类型。编译器在构建阶段进行检查。运行时没有意外异常——代码要么正常工作,要么无法编译。
\n\n性能:谁更快?
\n\n第二个重要方面是性能。在这方面,Dictionary再次领先。为什么?关键在于装箱(boxing)和拆箱(unboxing)。
\n\n当你使用Hashtable时,所有值类型(int、double、struct等)都会被装箱为对象。这会给垃圾回收器带来负担并降低速度。让我们通过实践来验证:
\n\n// Hashtable:装箱/拆箱\nHashtable ht = new Hashtable();\nStopwatch sw = Stopwatch.StartNew();\nfor (int i = 0; i < 1000000; i++)\n{\n ht.Add(i, i); // 每个int都被装箱为object\n}\nsw.Stop();\nConsole.WriteLine($"Hashtable添加:{sw.ElapsedMilliseconds} 毫秒");\n\n// Dictionary:无装箱\nDictionary<int, int> dict = new Dictionary<int, int>();\nsw.Restart();\nfor (int i = 0; i < 1000000; i++)\n{\n dict.Add(i, i); // 无装箱——直接操作int\n}\nsw.Stop();\nConsole.WriteLine($"Dictionary添加:{sw.