为啥字典在 C# 中优于 Hashtable?

Posted

技术标签:

【中文标题】为啥字典在 C# 中优于 Hashtable?【英文标题】:Why is Dictionary preferred over Hashtable in C#?为什么字典在 C# 中优于 Hashtable? 【发布时间】:2010-09-23 00:23:20 【问题描述】:

在大多数编程语言中,字典比哈希表更受欢迎。 背后的原因是什么?

【问题讨论】:

> 这不一定是真的。哈希表是字典的实现。一个典型的,它可能是 .NET 中的默认值,但根据定义它不是唯一的。我不确定这是否是 ECMA 标准所要求的,但MSDN documentation 非常清楚地指出它是作为哈希表实现的。当替代方案更合理时,它们甚至提供 SortedList 类。 @Promit 我一直认为DictionaryHashtable 的实现。 我认为原因是,您可以在字典中定义键的类型和自拍的值。 Hashtable 只能获取对象并根据哈希值保存对(来自 object.GetHashCode() )。 @Dan 你的说法是完全错误的......哈希表只包含每个键的一个实例,并且搜索永远不会产生多个条目;如果要将多个值与每个键相关联,请将哈希表值设为值列表。没有像“字典”这样的数据结构......字典只是一些库用于其哈希表的名称。例如,C# 的非泛型哈希表称为HashTable。当他们将泛型添加到语言中时,他们将泛型版本称为Dictionary。两者都是哈希表。 @Dan 你的说法是错误的......哈希表 (en.wikipedia.org/wiki/Hash_table) 是字典的特定实现,也就是关联数组 (en.wikipedia.org/wiki/Associative_array),并且,作为字典,只有包含每个键的一个实例,并且搜索永远不会产生多个条目;如果要将多个值与每个键相关联,请将哈希表值设为值列表。 .NET Dictionary 和 Hashtable 类都是哈希表。 【参考方案1】:

就其价值而言,字典(概念上)一个哈希表。

如果您的意思是“为什么我们使用 Dictionary<TKey, TValue> 类而不是 Hashtable 类?”,那么答案很简单:Dictionary<TKey, TValue> 是泛型类型,Hashtable 不是。这意味着您可以使用 Dictionary<TKey, TValue> 获得类型安全,因为您不能将任何随机对象插入其中,并且您不必强制转换取出的值。

有趣的是,.NET Framework 中的Dictionary<TKey, TValue> 实现基于Hashtable,您可以从源代码中的这条评论看出:

通用字典是从 Hashtable 的源中复制的

Source

【讨论】:

而且通用集合更快,因为没有装箱/拆箱 不确定是否有上述语句的哈希表,但对于 ArrayList 与 List 是真的 Hashtable 使用 Object 在内部保存东西(只有非通用的方法)所以它也必须装箱/拆箱。 @BrianJ:“哈希表”(两个词)是这种结构的计算机科学术语;字典是一个具体的实现。 HashTable 大致对应于 Dictionary (尽管接口略有不同),但两者都是哈希表概念的实现。当然,为了进一步混淆问题,一些语言将它们的哈希表称为“字典”(例如 Python)——但正确的 CS 术语仍然是哈希表。 @BrianJ:HashTable(类)和Dictionary(类)都是哈希表(概念),但HashTable 不是Dictionary,也不是Dictionary HashTable。它们以非常相似的方式使用,Dictionary<Object,Object> 可以以与HashTable 相同的无类型方式运行,但它们不直接共享任何代码(尽管部分可能以非常相似的方式实现)。 【参考方案2】:

区别

Dictionary Hashtable
Generic Non-Generic
Needs own thread synchronization Offers thread safe version through Synchronized() method
Enumerated item: KeyValuePair Enumerated item: DictionaryEntry
Newer (> .NET 2.0) Older (since .NET 1.0)
is in System.Collections.Generic is in System.Collections
Request to non-existing key throws exception Request to non-existing key returns null
potentially a bit faster for value types bit slower (needs boxing/unboxing) for value types

相似之处:

两者都是内部哈希表 ==根据键快速访问多项数据 两者都需要不可变且唯一的键 两者的密钥都需要自己的GetHashCode()方法

替代 .NET 集合:

(代替字典和哈希表的候选)

ConcurrentDictionary - 线程安全(可以同时从多个线程安全访问) HybridDictionary - 优化性能(适用于少数项目,也适用于许多项目) OrderedDictionary - 值可以通过 int index 访问(按添加项目的顺序) SortedDictionary - 项目自动排序 StringDictionary - 强类型和为字符串优化(现在不推荐使用 Dictionary

【讨论】:

@Guillaume86,这就是为什么你使用 TryGetValue 而不是 msdn.microsoft.com/en-us/library/bb347013.aspx +1 for StringDictionary...btw StringDictionary 与使用默认构造函数时的 Dictionary<string, string> 不同。 ParallelExtensionsExtras @code.msdn.microsoft.com/windowsdesktop/… 包含一个 ObservableConcurrentDictionary,它是很好的绑定和并发。 很棒的解释,很高兴您还列出了相似之处以减少可能会想到的问题 ***.com/a/1089142/5035500, net-informations.com/faq/general/dictionary.htm, c-sharpcorner.com/blogs/…【参考方案3】:

因为Dictionary 是一个泛型类(Dictionary<TKey, TValue>),所以访问它的内容是类型安全的(即你不需要像使用Hashtable 那样从Object 进行转换)。

比较

var customers = new Dictionary<string, Customer>();
...
Customer customer = customers["Ali G"];

var customers = new Hashtable();
...
Customer customer = customers["Ali G"] as Customer;

但是,Dictionary 在内部实现为哈希表,因此从技术上讲,它的工作方式相同。

【讨论】:

【参考方案4】:

仅供参考:在 .NET 中,Hashtable 是线程安全的,可供多个读取线程和单个写入线程使用,而在 Dictionary 中,公共静态成员是线程安全的,但不保证任何实例成员都是线程安全的.

因此,我们不得不将所有字典改回Hashtable

【讨论】:

很有趣。 Dictionary 源代码看起来更加简洁和快速。使用 Dictionary 并实现自己的同步可能会更好。如果字典读取绝对需要是最新的,那么您只需同步对字典的读/写方法的访问。这将是很多锁定,但它会是正确的。 另外,如果您的读取不必绝对是最新的,您可以将字典视为不可变的。然后,您可以通过根本不同步读取来获取对 Dictionary 的引用并获得性能(因为它是不可变的并且本质上是线程安全的)。要更新它,您在后台构建字典的完整更新副本,然后只需将引用与 Interlocked.CompareExchange 交换(假设单个写入线程;多个写入线程将需要同步更新)。 .Net 4.0 添加了 ConcurrentDictionary 类,该类将所有公共/受保护的方法实现为线程安全的。如果您不需要支持旧平台,这可以让您替换多线程代码中的Hashtable:msdn.microsoft.com/en-us/library/dd287191.aspx 我记得读过 HashTable 仅在信息永远不会从表中删除的情况下是读写器线程安全的。如果读者在删除另一个项目时询问表中的项目,并且读者会在多个位置查找该项目,则可能在阅读器搜索时,作者可能会移动该项目从一个没有被检查的地方到一个已经检查过的地方,从而导致该项目不存在的虚假报告。【参考方案5】:

在 .NET 中,Dictionary&lt;,&gt;HashTable 之间的区别主要在于前者是泛型类型,因此您可以获得泛型在静态类型检查方面的所有好处(并减少装箱,但这不是t 与人们在性能方面倾向于认为的一样大 - 不过,拳击有一定的内存成本)。

【讨论】:

【参考方案6】:

人们说字典和哈希表是一样的。

这不一定是真的。哈希表是实现字典的一种方式。一个典型的,它可能是 .NET 中 Dictionary 类中的默认一个,但根据定义它不是唯一的。

您同样可以使用链表或搜索树来实现字典,只是效率不高(对于某些效率指标)。

【讨论】:

MS 文档说:“使用它的键检索一个值非常快,接近 O(1),因为 Dictionary )>) 类被实现为一个哈希表。” - 所以在处理Dictionary&lt;K,V&gt; 时应该保证一个哈希表。 IDictionary&lt;K,V&gt; 可以是任何东西,不过 :) @rix0rrr - 我认为你已经倒退了,字典使用哈希表而不是哈希表使用字典。 @JosephHamilton - rix0rrr 说对了:“哈希表 字典的实现。”他的意思是“字典”的概念,而不是类(注意小写)。从概念上讲,哈希表实现了字典接口。在 .NET 中,Dictionary 使用哈希表来实现 IDictionary。很乱;) 我在 .NET 中谈论,因为这是他在回复中引用的内容。 @JosephHamilton:implements(或implementation of)甚至与uses 的意思完全不同。恰恰相反。如果他说的稍有不同(但含义相同),也许会更清楚:“哈希表是实现字典的一种方式”。也就是说,如果您想要字典的功能,一种方法(实现字典)是使用哈希表。【参考方案7】:

Collections & Generics 对于处理对象组很有用。在 .NET 中,所有集合对象都位于接口IEnumerable 下,而该接口又具有ArrayList(Index-Value))HashTable(Key-Value)。在 .NET framework 2.0 之后,ArrayList & HashTable 被替换为 List & Dictionary。现在,ArraylistHashTable 在当今的项目中不再使用。

谈到HashTableDictionary 之间的区别,Dictionary 是通用的,而Hastable 不是通用的。我们可以将任何类型的对象添加到HashTable,但是在检索时我们需要将其转换为所需的类型。所以,它不是类型安全的。但是对于dictionary,我们可以在声明自己的同时指定key和value的类型,所以不需要在检索的时候进行强制转换。

我们来看一个例子:

哈希表

class HashTableProgram

    static void Main(string[] args)
    
        Hashtable ht = new Hashtable();
        ht.Add(1, "One");
        ht.Add(2, "Two");
        ht.Add(3, "Three");
        foreach (DictionaryEntry de in ht)
        
            int Key = (int)de.Key; //Casting
            string value = de.Value.ToString(); //Casting
            Console.WriteLine(Key + " " + value);
        

    

字典,

class DictionaryProgram

    static void Main(string[] args)
    
        Dictionary<int, string> dt = new Dictionary<int, string>();
        dt.Add(1, "One");
        dt.Add(2, "Two");
        dt.Add(3, "Three");
        foreach (KeyValuePair<int, String> kv in dt)
        
            Console.WriteLine(kv.Key + " " + kv.Value);
        
    

【讨论】:

我们可以使用 var 来代替显式分配 KeyValuePair 的数据类型。所以,这会减少打字 - foreach (var kv in dt)...只是一个建议。【参考方案8】:

字典:

如果我们试图找到一个不存在的键,它会返回/抛出异常。

它比 Hashtable 更快,因为没有装箱和拆箱。

只有公共静态成员是线程安全的。

字典是一种通用类型,这意味着我们可以将它与任何数据类型一起使用(创建时,必须为键和值指定数据类型)。

示例:Dictionary<string, string> <NameOfDictionaryVar> = new Dictionary<string, string>();

Dictionay 是 Hashtable 的类型安全实现,KeysValues 是强类型的。

哈希表:

如果我们试图找到一个不存在的键,它会返回 null。

它比字典慢,因为它需要装箱和拆箱。

Hashtable 中的所有成员都是线程安全的,

Hashtable 不是泛型,

Hashtable是松散类型的数据结构,我们可以添加任意类型的键和值。

【讨论】:

"如果我们试图找到一个不存在的键,它会返回/抛出异常。"如果您使用Dictionary.TryGetValue,则不会【参考方案9】:

MSDN 上的Extensive Examination of Data Structures Using C# 文章指出冲突解决策略也有区别:

Hashtable 类使用一种称为rehashing 的技术。

重新散列的工作原理如下:有一组散列不同的函数, H1 ... Hn,以及在从哈希中插入或检索项目时 表,最初使用 H1 散列函数。如果这导致 如果发生碰撞,则尝试 H2,如果需要,可以尝试 Hn

词典使用一种称为链接的技术。

使用重新散列,在发生冲突时重新计算散列,并尝试与散列对应的新槽。然而,对于链接,使用辅助数据结构来保存 任何碰撞。具体来说, Dictionary 中的每个插槽都有一个数组 映射到该存储桶的元素。发生碰撞时, 碰撞元素被添加到存储桶列表中。

【讨论】:

【参考方案10】:

从 .NET Framework 3.5 开始,还有一个 HashSet&lt;T&gt;,它提供了 Dictionary&lt;TKey, TValue&gt; 的所有优点,如果您只需要键而不需要值。

因此,如果您使用Dictionary&lt;MyType, object&gt; 并始终将值设置为null 来模拟类型安全哈希表,您可能应该考虑切换到HashSet&lt;T&gt;

【讨论】:

【参考方案11】:

Hashtable 是一种松散类型的数据结构,因此您可以向Hashtable 添加任何类型的键和值。 Dictionary 类是类型安全的Hashtable 实现,键和值是强类型的。创建Dictionary 实例时,必须同时指定键和值的数据类型。

【讨论】:

【参考方案12】:

注意the documentation 说:“Dictionary)>) 类实现为 哈希表”,而不是“Dictionary)>) 类实现为 HashTable"

字典不是作为哈希表实现的,而是按照哈希表的概念来实现的。由于使用了泛型,该实现与 HashTable 类无关,尽管 Microsoft 在内部可以使用相同的代码并将 Object 类型的符号替换为 TKey 和 TValue。

在 .NET 1.0 中不存在泛型;这是 HashTable 和 ArrayList 最初开始的地方。

【讨论】:

【参考方案13】:

哈希表:

键/值在存储到堆中时会转换为对象(装箱)类型。

从堆中读取时需要将键/值转换为所需的类型。

这些操作非常昂贵。我们需要尽可能避免装箱/拆箱。

字典: HashTable 的通用变体。

没有装箱/拆箱。无需转换。

【讨论】:

【参考方案14】:

Hashtable 对象由包含集合元素的桶组成。存储桶是 Hashtable 中元素的虚拟子组,与大多数集合相比,它使搜索和检索变得更容易和更快

Dictionary 类与 Hashtable 类具有相同的功能。对于值类型,特定类型(对象除外)比 Hashtable 具有更好的性能,因为 Hashtable 的元素属于 Object 类型,因此,如果存储或检索值类型。

进一步阅读:Hashtable and Dictionary Collection Types

【讨论】:

【参考方案15】:

另一个重要的区别是 Hashtable 是线程安全的。 Hashtable 具有内置的多读取器/单写入器 (MR/SW) 线程安全性,这意味着 Hashtable 允许一个写入器与多个读取器一起使用而无需锁定。

在 Dictionary 的情况下,没有线程安全;如果您需要线程安全,则必须实现自己的同步。

进一步阐述:

Hashtable 通过Synchronized 属性提供了一些线程安全性,该属性返回一个线程安全的集合包装。包装器通过在每次添加或删除操作时锁定整个集合来工作。因此,每个试图访问集合的线程都必须等待轮到它来获取一个锁。这是不可扩展的,并且可能导致大型集合的性能显着下降。此外,设计并未完全免受竞争条件的影响。

.NET Framework 2.0 集合类如List&lt;T&gt;, Dictionary&lt;TKey, TValue&gt; 等不提供任何线程同步;当在多个线程上同时添加或删除项目时,用户代码必须提供所有同步

如果您需要类型安全以及线程安全,请使用 .NET Framework 中的并发集合类。延伸阅读here。

另一个区别是,当我们在 Dictionary 中添加多个条目时,条目的添加顺序保持不变。当我们从 Dictionary 中检索项目时,我们将以插入它们的相同顺序获取记录。而 Hashtable 不保留插入顺序。

【讨论】:

据我了解,Hashset不涉及删除的使用场景中保证MR/SW线程安全。我认为这可能是为了完全保证 MR/SW 安全,但是安全地处理删除会大大增加 MR/SW 安全的费用。虽然Dictionary 的设计可以在不可删除场景中以最低成本提供 MR/SW 安全性,但我认为 MS 希望避免将不可删除场景视为“特殊”。【参考方案16】:

我能弄清楚的另一个区别是:

我们不能在 Web 服务中使用 Dictionary(泛型)。原因是没有 Web 服务标准支持泛型标准。

【讨论】:

我们可以在基于soap的网络服务中使用通用列表(List)。但是,我们不能在 Web 服务中使用字典(或哈希表)。我认为这是因为 .net xmlserializer 无法处理字典对象。【参考方案17】:

Dictionary&lt;&gt; 是泛型类型,因此它是类型安全的。

您可以在 HashTable 中插入任何值类型,这有时可能会引发异常。但是Dictionary&lt;int&gt; 只会接受整数值,同样Dictionary&lt;string&gt; 只会接受字符串。

所以,最好使用Dictionary&lt;&gt; 而不是HashTable

【讨论】:

【参考方案18】:

在大多数编程语言中,字典比哈希表更受欢迎

我认为这不一定是真的,大多数语言都有其中一种,具体取决于terminology they prefer。

然而,在 C# 中,明确的原因(对我来说)是 C# HashTables 和 System.Collections 命名空间的其他成员在很大程度上已经过时。它们出现在 c# V1.1 中。从 C# 2.0 开始,它们已被 System.Collections.Generic 命名空间中的通用类取代。

【讨论】:

哈希表相对于字典的优势之一是,如果字典中不存在某个键,则会抛出错误。如果哈希表中不存在某个键,则它只返回 null。 在 C# 中,我仍然会避免使用 System.Collections.Hashtable,因为它们没有泛型的优势。如果不知道键是否存在,可以使用 Dictionary 的 TryGetValue 或 HasKey。 哎呀,不是 HasKey,应该是 ContainsKey。【参考方案19】:

根据我使用.NET Reflector看到的:

[Serializable, ComVisible(true)]
public abstract class DictionaryBase : IDictionary, ICollection, IEnumerable

    // Fields
    private Hashtable hashtable;

    // Methods
    protected DictionaryBase();
    public void Clear();
.
.
.

Take note of these lines
// Fields
private Hashtable hashtable;

所以我们可以确定 DictionaryBase 在内部使用了一个 HashTable。

【讨论】:

System.Collections.Generic.Dictionary 不是从 DictionaryBase 派生的。 "所以我们可以确定 DictionaryBase 在内部使用了一个 HashTable。" -- 很好,但与问题无关。

以上是关于为啥字典在 C# 中优于 Hashtable?的主要内容,如果未能解决你的问题,请参考以下文章

dotnet C# 字典 Dictionary 和 Hashtable 的性能对比

C#中HashTable用法和Dictionary比较

何时何地使用 Dictionary verses Hashtable? [复制]

Redis学习系列四Hash(字典)

为啥 .Net 字典中的条目要另外排序?

调试 C# 时为啥不能更改字典键值对的计数?