为什么在C#中字典比哈希表更受青睐?


1395

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


21
>这不一定是正确的。哈希表是字典的实现。那是一个典型的例子,它可能是.NET中的默认例子,但根据定义,它并不是唯一的一个。我不确定ECMA标准是否要求这样做,但是MSDN文档非常清楚地将其称为以哈希表的形式实现。他们甚至在替代方法更合理的时候提供SortedList类。
09年

15
@Promit我一直以为Dictionary是的实现Hashtable
b1nary.atr0phy 2015年

2
我认为原因是,在字典中您可以定义键的类型和您的selfe的值。Hashtable只能获取对象,并基于哈希(来自object.GetHashCode())保存对。
Radinator

2
@Dan您的主张是非常错误的……哈希表仅包含每个键的一个实例,并且搜索从不产生多个条目;如果要与每个键关联多个值,请使哈希表值成为值列表。没有像“字典”这样的数据结构。字典只是一些库用于其哈希表的名称。例如,C#的非通用哈希表称为HashTable。当他们在语言中添加泛型时,他们称为泛型版本Dictionary。两者都是哈希表。
Jim Balter

3
@Dan您的主张是错误的...哈希表(en.wikipedia.org/wiki/Hash_table)是字典的一种特殊实现,又名关联数组(en.wikipedia.org/wiki/Associative_array),并且一本字典,每个键只包含一个实例,搜索永远不会产生多个条目;如果要与每个键关联多个值,请使哈希表值成为值列表。.NET字典和Hashtable类都是哈希表。
Jim Balter

Answers:


1568

对于它的价值,字典概念上一个哈希表。

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

有趣的是,Dictionary<TKey, TValue>.NET Framework中的实现基于Hashtable,您可以从其源代码中的此注释中看出:

通用字典是从Hashtable的来源复制而来的

资源


393
而且通用集合的速度要快得多,因为没有装箱/拆箱
Chris S

6
不知道上面的语句是否有哈希表,但是对于ArrayList vs List <t>是真的
Chris S

36
Hashtable使用Object在内部保存事物(只有非通用的方式可以做到),因此它也必须装箱/拆箱。
Guvante

16
@BrianJ:“哈希表”(两个词)是这种结构的计算机科学术语;字典是一个特定的实现。HashTable大致对应于Dictionary <object,object>(尽管接口略有不同),但是两者都是哈希表概念的实现。当然,只是为了进一步混淆,某些语言将其哈希表称为“字典”(例如Python)-但正确的CS术语仍然是哈希表。
Michael Madsen 2013年

32
@BrianJ:HashTable(class)和Dictionary(class)都是哈希表(concept),但是a HashTable不是a Dictionary,也不是Dictionarya HashTable。它们以非常相似的方式使用,并且Dictionary<Object,Object>可以以与a相同的无类型方式起作用HashTable,但是它们不直接共享任何代码(尽管部分可能以非常相似的方式实现)。
Michael Madsen 2013年

625

Dictionary<<< >>> Hashtable差异:

  • 通用 <<< >>> 非通用
  • 需要自己的线程同步 <<< >>> 通过方法提供线程安全版本Synchronized()
  • 枚举项目:KeyValuePair<<< >>>枚举项目:DictionaryEntry
  • 较新(> .NET 2.0)<<< >>>较旧(自.NET 1.0起
  • System.Collections中。通用 <<< >>>在System.Collections中
  • 请求不存在的键引发异常 <<< >>>请求不存在的键返回null
  • 对于值类型,可能会更快一些 <<< >>> 对于值类型,可能会慢一些(需要装箱/拆箱)

Dictionary/ Hashtable相似之处:

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

相似的 .NET集合(代替字典和哈希表使用的候选人):

  • ConcurrentDictionary- 线程安全(可以同时从多个线程安全地访问)
  • HybridDictionary- 优化的性能(适用于少量物品,也适用于许多物品)
  • OrderedDictionary-可以通过int索引(按添加项目的顺序)访问
  • SortedDictionary- 自动排序的项目
  • StringDictionary-强类型化并针对字符串进行了优化

11
@ Guillaume86,这就是为什么您使用TryGetValue而不是msdn.microsoft.com/en-us/library/bb347013.aspx
Trident D'Gao

2
StringDictionary... btw的+1 StringDictionaryDictionary<string, string>使用默认构造函数时不同。
成臣

ParallelExtensionsExtras @ code.msdn.microsoft.com / windowsdesktop/…包含一个ObservableConcurrentDictionary,它具有出色的冷杉绑定性和并发性。
VoteCoffee 2014年

3
很棒的解释,很高兴您也列出了相似之处,以减轻人们可能想到的问题
mkb


178

因为Dictionary是通用类(Dictionary<TKey, TValue>),所以访问其内容是类型安全的(即,您不需要Object像一样从进行强制转换Hashtable)。

相比

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

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

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


88

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

因此,我们必须将所有词典重新更改Hashtable为。


10
好玩 Dictionary <T>源代码看起来更干净,更快。最好使用Dictionary并实现自己的同步。如果Dictionary的读取绝对需要是最新的,那么您只需要同步对Dictionary的读/写方法的访问即可。将会有很多锁定,但这是正确的。
Triynko 2010年

10
另外,如果您的阅读不一定是最新的,则可以将字典视为不可变的。然后,您可以通过完全不同步读​​取来获取对Dictionary的引用并获得性能(因为它是不可变的,并且本质上是线程安全的)。要对其进行更新,您可以在后台构造Dictionary的完整更新副本,然后仅将引用与Interlocked.CompareExchange交换(假设有一个写入线程;多个写入线程将需要同步更新)。
Triynko 2010年

38
.Net 4.0添加了ConcurrentDictionary该类,该类具有实现为线程安全的所有公共/受保护方法。如果您不需要支持旧平台,则可以替换Hashtable多线程代码中的代码:msdn.microsoft.com/en-us/library/dd287191.aspx
Dan由Firelight摆弄2012年

匿名救援。很酷的答案。
unkulunkulu 2012年

5
我记得曾经读过HashTable在信息永远不会从表中删除的情况下仅是读写器线程安全的。如果在删除另一个项目时读者要求在表中的项目,并且读者将在一个以上的位置查找该项目,则当读者在搜索时,作者可能会移动该项目从一个未被检查的地方到一个未被检查的地方,从而导致该项目不存在的错误报告。
2013年

68

在.NET之间的差异Dictionary<,>,并HashTable主要是前者是一个泛型类型,所以你在静态类型检查(并降低拳击而言仿制药的所有好处,但这不是大如人们往往认为在性能方面-拳击有一定的内存成本)。


34

人们说字典与哈希表相同。

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

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


4
MS docs说:“使用键检索值非常快,接近O(1),因为Dictionary <(Of <(TKey,TValue>)>)类被实现为哈希表。” -因此在处理时应确保您有哈希表Dictionary<K,V>IDictionary<K,V>可能是什么,但:)
snemarch

13
@ rix0rrr-我认为您已经倒过来了,Dictionary使用HashTable,而不是HashTable使用Dictionary。
约瑟夫·汉密尔顿

8
@JosephHamilton-rix0rrr正确地指出:“哈希表字典的实现。” 他的意思是“字典”,而不是阶级(注意小写)。从概念上讲,哈希表实现了字典接口。在.NET中,Dictionary使用哈希表来实现IDictionary。太乱了;)
罗伯特·亨辛

我在.NET中谈论的是,因为这就是他在答复中引用的内容。
约瑟夫·汉密尔顿

2
@JosephHamilton:工具(或执行)甚至不远程意味着同样的事情用途。恰恰相反。如果他说的稍有不同(但含义相同),可能会更清楚:“哈希表是实现字典的一种方式”。也就是说,如果您想要字典的功能,那么一种实现实现字典)的方法是使用哈希表。
制造商史蒂夫(Steve)2015年

21

CollectionsGenerics对于处理对象组很有用。在.NET中,所有collections对象都位于该接口下IEnumerable,而该接口又具有ArrayList(Index-Value))HashTable(Key-Value)。在.NET Framework 2.0之后,ArrayListHashTable替换为ListDictionary。现在,ArraylistHashTable不再在当今的项目中使用。

来到之间的区别HashTableDictionaryDictionary是通用的,其中如Hastable不通用。我们可以向添加任何类型的对象HashTable,但是在检索时,我们需要将其强制转换为所需的类型。因此,它不是类型安全的。但是,对于dictionary,我们可以在声明自身的同时指定键和值的类型,因此无需在检索时进行强制转换。

让我们看一个例子:

哈希表

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);
        }
    }
}

2
可以使用var来代替为KeyValuePair显式分配数据类型。因此,这将减少键入-foreach(dt中的var kv)...只是一个建议。
罗恩

16

字典:

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

  • 它比哈希表更快,因为没有装箱和拆箱。

  • 仅公共静态成员是线程安全的。

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

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

  • Dictionay是Hashtable的类型安全实现,Keys并且Values具有强类型。

哈希表:

  • 如果我们尝试查找不存在的键,则返回null。

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

  • 哈希表中的所有成员都是线程安全的,

  • 哈希表不是通用类型,

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


“如果我们尝试查找不存在的密钥,它将返回/引发异常。” 如果您使用,请不要使用Dictionary.TryGetValue
Jim Balter '18

16

MSDN上有关使用C#进行数据结构广泛检查的内容指出,冲突解决策略也存在差异 :

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

重新哈希的工作方式如下:有一组哈希不同的函数H 1 ... H n,当从哈希表插入或检索项目时,最初使用H 1哈希函数。如果这导致了碰撞,H 2试图代替,并开始长达^ h ñ如果需要的话。

字典使用称为链接的技术。

通过重新哈希,在发生冲突的情况下,可以重新计算哈希,并尝试与哈希相对应的新插槽。但是,通过链接,可以使用辅助数据结构来保存任何冲突。具体来说,字典中的每个插槽都有一个映射到该存储桶的元素数组。在发生碰撞的情况下,碰撞元素位于铲斗列表的前面。


16

从.NET Framework 3.5开始HashSet<T>Dictionary<TKey, TValue>如果只需要键而不需要值,则还提供了的所有优点。

因此,如果您使用a Dictionary<MyType, object>并始终将其值设置null为模拟类型安全哈希表,则应考虑切换到HashSet<T>


14

Hashtable是弱类型的数据结构,这样你就可以添加任何类型的的密钥和值Hashtable。该Dictionary班是一种安全的Hashtable实现和键和值是强类型。创建Dictionary实例时,必须同时指定键和值的数据类型。


11

注意,MSDN说:“ Dictionary <(Of <(TKey,TValue>)>)类被实现为哈希表 ”,而不是“ Dictionary <(Of <(TKey,TValue>)>)类被实现为HashTable ”。

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

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


您可以修复该MSDN报价吗?缺少或错误的东西;它不是语法上的,而且有点难以理解。
彼得·莫滕森

10

哈希表:

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

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

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

字典: HashTable的通用变体。

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


8

Hashtable对象由包含集合元素的存储桶组成。存储桶是哈希表中元素的虚拟子组,与大多数集合相比,存储桶使搜索和检索更加轻松快捷

Dictionary类具有与Hashtable类相同的功能。特定类型的字典(对象以外的字典)比哈希表具有更好的性能对于值类型,因为哈希表的元素是对象类型,因此,如果存储或检索值类型,则装箱和拆箱通常会发生。

进一步阅读:哈希表和字典集合类型


7

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

对于Dictionary,没有线程安全性;如果需要线程安全,则必须实现自己的同步。

详细说明:

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

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

如果需要类型安全性和线程安全性,请在.NET Framework中使用并发集合类。在这里进一步阅读。

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


据我了解,在不涉及删除的Hashset使用情况下保证MR / SW线程安全。我认为它可能是完全MR / SW安全的,但是安全地处理删除操作会大大增加MR / SW安全的费用。尽管Dictionary在无删除方案中本可以以最低的成本提供MR / SW安全性,但我认为MS希望避免将无删除方案视为“特殊”情况。
超级猫

5

我可以找出的另一个区别是:

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


我们可以在基于soap的Web服务中使用通用列表(List <string>)。但是,我们不能在Web服务中使用字典(或哈希表)。我认为这是因为.net xmlserializer无法处理字典对象。
悉达思2015年

5

Dictionary<> 是通用类型,因此类型安全。

您可以在HashTable中插入任何值类型,这有时可能会引发异常。但是Dictionary<int>将仅接受整数值,并且类似地Dictionary<string>将仅接受字符串。

因此,最好使用Dictionary<>代替HashTable


0

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

我认为这不一定是正确的,大多数语言都有一种或另一种,这取决于他们喜欢术语

但是,在C#中,(对我而言)明显的原因是C#HashTables和System.Collections命名空间的其他成员已经过时了。它们存在于c#V1.1中。它们已从C#2.0中由System.Collections.Generic命名空间中的Generic类替换。


哈希表优于字典的优点之一是,如果字典中不存在键,则它将引发错误。如果哈希表中不存在键,则仅返回null。
比尔·诺曼

在C#中,我仍然避免使用System.Collections.Hashtable,因为它们没有泛型的优势。如果您不知道该键是否存在,则可以使用Dictionary的TryGetValue或HasKey。
kristianp

哎呀,不是HasKey,应该是ContainsKey。
kristianp

-3

根据我使用.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。


16
System.Collections.Generic.Dictionary <TKey,TValue>并非从DictionaryBase派生。
snemarch

“因此,我们可以确保DictionaryBase在内部使用HashTable。” -很好,但是与问题无关。
吉姆·巴尔特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.