Чому Словник віддається перевазі над Hashtable в C #?


1395

У більшості мов програмування словники переважні над хеш-таблицями. Які причини цього обумовлені?


21
> Це не обов'язково вірно. Хеш-таблиця - це реалізація словника. Типовий для цього, і він може бути типовим у .NET, але це не за визначенням єдиний. Я не впевнений, що цього вимагає стандарт ECMA, але документація MSDN дуже чітко називає його реалізованим як хеш-таблицю. Вони навіть надають клас SortedList для тих випадків, коли альтернатива є більш розумною.
Обіцяйте

15
@Promit Я завжди вважав, що Dictionaryце реалізація Hashtable.
b1nary.atr0phy

2
Я думаю, що причина полягає в тому, що в словнику ви можете визначити тип ключа і значення для вашого selfe. Hashtable може приймати лише об'єкти і зберігає пари на основі хеша (from object.GetHashCode ()).
Радіатор

2
@Dan Ваша претензія досить помилкова ... хеш-таблиця містить лише один екземпляр кожного ключа, а пошук ніколи не дає декількох записів; якщо ви хочете пов’язати кілька значень з кожною клавішею, складіть значення таблиці хеш-списку список значень. Немає такої структури даних, як "Словник" ... Словник - це просто назва, яке деякі бібліотеки використовують для своєї хеш-таблиці. наприклад, називається негенерована хеш-таблиця C # HashTable. Коли вони додали до мови генерики, вони назвали родову версію Dictionary. Обидва є хеш-таблицями.
Джим Балтер

3
@Dan Ваша претензія помилкова ... хеш-таблиця ( en.wikipedia.org/wiki/Hash_table ) - це особлива реалізація словника, яка називається асоціативним масивом ( en.wikipedia.org/wiki/Associative_array ), і, будучи словник, містить лише один екземпляр кожного ключа, і пошук ніколи не дає декількох записів; якщо ви хочете пов’язати кілька значень з кожною клавішею, складіть значення таблиці хеш-списку список значень. І .NET Словник, і класи Hashtable - це хеш-таблиці.
Джим Балтер

Відповіді:


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, щоб утримувати речі всередині (Тільки не загальний спосіб це зробити), тому він також повинен був упакувати / розблокувати.
Гуванте

16
@BrianJ: "хеш-таблиця" (два слова) - це термін інформатики для такої структури; Словник - це конкретна реалізація. HashTable приблизно відповідає словнику <об'єкт, об'єкт> (хоча і з дещо різними інтерфейсами), але обидва є реалізацією концепції хеш-таблиці. І звичайно, для подальшого плутання питань, деякі мови називають свої хеш-таблиці «словниками» (наприклад, Python) - але належний термін CS все ще є хеш-таблицею.
Майкл Мадсен

32
@BrianJ: І HashTable(клас), і Dictionary(клас) є хеш-таблицями (концепція), але а HashTableне є Dictionary, а не Dictionarya HashTable. Вони використовуються в дуже схожих формах і Dictionary<Object,Object>можуть діяти так само, як типичний спосіб HashTable, але це не стосується безпосередньо коду (хоча частини, ймовірно, будуть реалізовані дуже схожим чином).
Майкл Мадсен

625

Dictionary<<< >>> Hashtableвідмінності:

  • Загальне <<< >>> Негенеричне
  • Потрібна власна синхронізація потоку <<< >>> Пропонує безпечну версію потоку за допомогою Synchronized()методу
  • Перерахуваний елемент: KeyValuePair<<< >>> Перелічений елемент:DictionaryEntry
  • Новіше (> .NET 2.0 ) <<< >>> Старіше (оскільки .NET 1.0 )
  • знаходиться в System.Collections.Generic <<< >>> знаходиться в 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
+1 для StringDictionary... btw StringDictionaryне є тим самим, як Dictionary<string, string>при використанні конструктора за замовчуванням.
Чен Чен

ParallelExtensionsExtras @ code.msdn.microsoft.com/windowsdesktop/… містить ObservableConcurrentDictionary , який чудово пов’язує ялицю, а також паралельність.
VoteCoffee

3
дивовижне пояснення, це дуже приємно, що ви також перерахували схожість, щоб зменшити питання, які можуть вам
спадати


178

Оскільки Dictionaryце загальний клас ( Dictionary<TKey, TValue>), тож доступ до його вмісту є безпечним для типу (тобто вам не потрібно робити передачу Object, як це робиться з a 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

FYI: In .NET Hashtableє потоком безпечним для використання декількома потоками читачів та однією записуючою ниткою, тоді як у Dictionaryзагальнодоступних статичних членів безпечна нитка, але будь-які члени екземплярів не гарантують, що вони є безпечними для потоків.

HashtableЧерез це нам довелося змінити всі наші словники .


10
Весело. Вихідний код словника <T> виглядає набагато чистіше і швидше. Можливо, буде краще використовувати словник та здійснити власну синхронізацію. Якщо у словнику читання абсолютно потрібно бути поточним, вам просто доведеться синхронізувати доступ до методів читання / запису Словника. Замикання було б дуже багато, але це було б правильно.
Трайко

10
Крім того, якщо ваші читання не повинні бути абсолютно актуальними, ви можете трактувати словник як незмінний. Тоді ви можете захопити посилання на словник і отримати продуктивність, не синхронізуючи читання взагалі (оскільки це незмінне і по суті є безпечним для потоків). Щоб оновити його, ви створюєте повну оновлену копію Словника у фоновому режимі, після чого просто поміняєте посилання на Interlocked.CompareExchange (припускаючи одну нитку запису; для декількох записів потрібно буде синхронізувати оновлення).
Трайко

38
.Net 4.0 додав ConcurrentDictionaryклас, у якому всі публічні / захищені методи реалізовані як безпечні для потоків. Якщо вам не потрібно підтримувати застарілі платформи, це дозволить вам замінити Hashtableбагатопотоковий код: msdn.microsoft.com/en-us/library/dd287191.aspx
Dan Is Fiddling Firelight

анонімний на допомогу. Класна відповідь.
unkulunkulu

5
Я пригадую, що читав, що HashTable - це лише безпека для читання та прочитання в сценарії, коли інформація ніколи не видаляється зі таблиці. Якщо читач запитує предмет, який знаходиться в таблиці, коли інший елемент видаляється, і читач шукає його в більш ніж одному місці, можливо, поки читач шукає письменника, він може перемістити предмет від місця, яке не було оглянуто, до місця, яке має, таким чином, у результаті з’являється помилкове повідомлення про те, що елемент не існує.
supercat

68

У .NET, різниця між Dictionary<,>і HashTable, головним чином, полягає в тому, що колишній - це загальний тип, тому ви отримуєте всі переваги дженериків у плані перевірки статичного типу (і зменшеного боксу, але це не так вже й багато, як люди схильні думати в терміни продуктивності - все ж є певна вартість пам'яті для боксу).


34

Люди кажуть, що Словник - це те саме, що і хеш-таблиця.

Це не обов'язково правда. Хеш-таблиця - один із способів реалізації словника. Типовий для цього, і він може бути типовим у .NET у Dictionaryкласі, але це не за визначенням єдиний.

Ви можете однаково добре реалізувати словник за допомогою зв'язаного списку або дерева пошуку, він просто не буде настільки ефективним (для деякої метрики ефективності).


4
Документи MS кажуть: "Отримання значення за допомогою його ключа дуже швидко, близьке до O (1), тому що клас Словник <(Of <(TKey, TValue>)>) реалізований як хеш-таблиця." - тож вам слід гарантувати хештейн при роботі Dictionary<K,V>. IDictionary<K,V>могло б бути що завгодно :)
снімар

13
@ rix0rrr - Я думаю, що ви отримали це назад, словник використовує HashTable, а не HashTable використовує словник.
Джозеф Гамільтон

8
@JosephHamilton - rix0rrr правильно зрозумів: "Хеш-таблиця - це реалізація словника ". Він має на увазі поняття "словник", а не клас (зверніть увагу на нижній регістр). Концептуально хеш-таблиця реалізує інтерфейс словника. У .NET, словник використовує хеш-таблицю для реалізації IDictionary.
Брудно

Я говорив про .NET, оскільки саме на це він посилався у своїй відповіді.
Джозеф Гамільтон

2
@JosephHamilton: реалізація (або реалізація ) навіть далеко не означає те саме, що використовує . Зовсім навпаки. Можливо, було б зрозуміліше, якби він сказав це дещо інакше (але з тим самим значенням): "хеш-таблиця - це один із способів реалізації словника". Тобто, якщо ви хочете функціонувати словник, один із способів зробити це ( реалізувати словник) - використовувати хештеб.
ToolmakerSteve

21

Collections& Genericsкорисні для обробки групи об'єктів. У .NET всі об'єкти колекцій потрапляють під інтерфейс IEnumerable, який у свою чергу має ArrayList(Index-Value))& HashTable(Key-Value). Після .NET Framework 2.0, ArrayList& HashTableбули замінені на List& Dictionary. Тепер Arraylist& & HashTableбільше не використовуються в сучасних проектах.

Переходячи до різниці між HashTable& Dictionary, Dictionaryє загальним, де як Hastableце не є загальним. Ми можемо додати до будь-якого типу об’єкт HashTable, але під час отримання нам потрібно привести його до потрібного типу. Отже, це не безпечно. Але до того dictionary, заявляючи про себе, ми можемо вказати тип ключа та значення, тому немає необхідності робити це під час пошуку.

Розглянемо приклад:

HashTable

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
замість явного призначення типу даних для KeyValuePair, ми могли б використовувати var. Отже, це зменшило б текст - foreach (var kv in dt) ... просто пропозиція.
Рон

16

Словник:

  • Він повертає / кидає виняток, якщо ми спробуємо знайти ключ, який не існує.

  • Це швидше, ніж Hashtable, тому що немає боксу та розпакування.

  • Тільки державні статичні члени захищені потоком.

  • Словник - це загальний тип, який означає, що ми можемо використовувати його з будь-яким типом даних (Під час створення необхідно вказати типи даних як для ключів, так і для значень).

    Приклад: Dictionary<string, string> <NameOfDictionaryVar> = new Dictionary<string, string>();

  • Dictionay є тіпобезопасним реалізація Hashtable, Keysі Valuesє строго типізований.

Hashtable:

  • Він повертається нульовим, якщо ми спробуємо знайти ключ, який не існує.

  • Це повільніше, ніж словник, оскільки він вимагає боксу та розпакування.

  • Усі члени в Hashtable є безпечними для потоків,

  • Хештейл не є родовим типом,

  • Хештеб - це структура даних із слабким типом, ми можемо додавати ключі та значення будь-якого типу.


"Він повертає / кидає виняток, якщо ми спробуємо знайти ключ, який не існує." Якщо ви не використовуєтеDictionary.TryGetValue
Джим Балтер

16

Широке Дослідження структур даних з допомогою C # статті на MSDN стверджує , що існує також різниця в стратегії дозволу зіткнень :

Клас Хештел використовує техніку, яку називають повторною проривкою .

Повторна робота працює наступним чином: існує набір різних хеш-функцій, H 1 ... H n , а при вставці або виведенні елемента з хеш-таблиці спочатку використовується хеш-функція H 1 . Якщо це призводить до зіткнення, замість цього спробується H 2 , а в разі потреби - до H n .

У словнику використовується техніка, яку називають ланцюжком .

Під час повторного прошивання, у випадку зіткнення, хеш перераховується, і новий слот, що відповідає хешу, буде випробуваний. Однак при ланцюговому використанні вторинна структура даних використовується для утримання будь-яких зіткнень . Зокрема, кожен слот у словнику має масив елементів, які відображають це відро. У разі зіткнення стикається елемент передує списку відра.


16

Оскільки .NET Framework 3.5 є також a, HashSet<T>який надає всі плюси, Dictionary<TKey, TValue>якщо вам потрібні лише ключі та відсутні значення.

Отже, якщо ви використовуєте a Dictionary<MyType, object>і завжди встановлюєте значення для nullімітації безпечної хеш-таблиці типу, можливо, вам слід подумати про перехід на HashSet<T>.


14

Ця Hashtableструктура даних має вільний тип, тому ви можете додавати ключі та значення будь-якого типу до Hashtable. DictionaryКлас є тіпобезопаснимі Hashtableреалізаціями, а ключі і значення сильно типізований. Під час створення Dictionaryекземпляра потрібно вказати типи даних як для ключа, так і для значення.


11

Зауважте, що MSDN говорить: "Клас словник <(Of <(TKey, TValue>)>) реалізований як хеш-таблиця ", а не "Словник <(Of <(TKey, TValue>)>) клас реалізований як HashTable "

Словник НЕ реалізується як HashTable, але він реалізується відповідно до концепції хеш-таблиці. Реалізація не пов'язана з класом HashTable через використання Generics, хоча внутрішньо Microsoft могла використати той самий код і замінити символи типу Object на TKey і TValue.

У .NET 1.0 Generics не існувало; саме тут спочатку починалися HashTable і ArrayList.


Чи можете ви виправити цю цитату MSDN? Щось відсутнє чи неправильне; це не граматично і дещо незрозуміло.
Пітер Мортенсен

10

HashTable:

Ключ / значення буде перетворено в тип об'єкта (бокс) під час зберігання в купу.

Ключ / значення потрібно перетворити в потрібний тип під час читання з купи.

Ці операції дуже затратні. Нам потрібно максимально уникати боксу / розпакування.

Словник: Загальний варіант HashTable.

Ніякого боксу / розпакування. Не потрібно конверсій.


8

Об'єкт Hashtable складається з відра, що містять елементи колекції. Відро - це віртуальна підгрупа елементів в межах Hashtable, що робить пошук і витягнення простішим і швидшим, ніж у більшості колекцій .

Клас Словник має таку ж функціональність, як і клас Hashtable. Словник конкретного типу (окрім Object) має кращі показники, ніж Hashtable для типів значень, тому що елементи Hashtable мають тип Object, і, отже, бокс та розпакування зазвичай виникають при зберіганні чи пошуку типу значень.

Для подальшого читання: Типи колекції Hashtable та Dictionary


7

Ще одна важлива відмінність полягає в тому, що Hashtable є безпечним для ниток. Hashtable має вбудовану безпеку нитки для читання / одиночного запису (MR / SW), а це означає, що Hashtable дозволяє ОДНОМУ автору разом з декількома читачами без блокування.

У випадку Словника немає безпеки потоку; якщо вам потрібна безпека потоку, ви повинні здійснити власну синхронізацію.

Детальніше:

Хештейл забезпечує деяку безпеку ниток через Synchronizedвластивість, що повертає захисну нитку обгортку навколо колекції. Обгортка працює, блокуючи всю колекцію під час кожного додавання або видалення. Тому кожен потік, який намагається отримати доступ до колекції, повинен дочекатися своєї черги, щоб зняти один замок. Це не масштабується і може призвести до значної погіршення продуктивності для великих колекцій. Також конструкція не повністю захищена від перегонів.

Класи колекції .NET Framework 2.0, подібні List<T>, Dictionary<TKey, TValue>тощо, не забезпечують синхронізації потоків; код користувача повинен забезпечувати всю синхронізацію, коли елементи додаються або видаляються на декількох потоках одночасно

Якщо вам потрібна безпека типу, а також безпека потоку, використовуйте паралельні класи колекцій у .NET Framework. Подальше читання тут .

Додаткова відмінність полягає в тому, що коли ми додаємо декілька записів у словник, зберігається порядок, в якому вони додаються. Коли ми витягнемо елементи зі словника, ми отримаємо записи в тому ж порядку, в який ми їх вставили. Тоді як Hashtable не зберігає порядок вставки.


Як я розумію, Hashsetгарантії безпеки потоку MR / SW у сценаріях використання , які не передбачають видалення . Я думаю, це, можливо, передбачалося повністю безпечно для MR / SW, але безпечне поводження з видаленнями значно збільшує витрати на безпеку MR / SW. Хоча дизайн Dictionaryміг запропонувати безпеку MR / SW за мінімальних витрат у сценаріях без видалення, я думаю, що MS хочуть уникнути трактування сценаріїв без видалення як "особливих".
supercat

5

Ще одна відмінність, яку я можу зрозуміти:

Ми не можемо використовувати словник <KT, VT> (generics) з веб-сервісами. Причина в тому, що жоден стандарт веб-сервісу не підтримує стандарт генерики.


Ми можемо використовувати загальні списки (Список <рядок>) у веб-сервісі на основі мила. Але ми не можемо використовувати словник (або хештел) у веб-сервісі. Я думаю, що причина цього в тому, що .net xmlserializer не може обробити словниковий об’єкт.
Сіддхарт

5

Dictionary<> - це загальний тип, і тому він безпечний.

Ви можете вставити будь-який тип значення в HashTable, і іноді це може бути винятком. Але Dictionary<int>прийматимуть лише цілі значення і аналогічно Dictionary<string>прийматимуть лише рядки.

Отже, краще використовувати Dictionary<>замість HashTable.


0

У більшості мов програмування словники переважні над хеш-таблицями

Я не думаю, що це обов'язково правда, більшість мов мають ту чи іншу, залежно від термінології, яку вони надають перевагу .

Однак у C # чітка причина (для мене) полягає в тому, що C # HashTables та інші члени простору імен System.Collections значною мірою застаріли. Вони були присутні в c # V1.1. Їх замінили з C # 2.0 класами Generic у просторі імен System.Collections.Generic.


Однією з переваг хештеля над словником є ​​те, що якщо ключ у словнику не існує, він видасть помилку. Якщо ключ не існує в хеш-файлі, він просто повертає null.
Білл Норман

У C # я все одно уникаю використання System.Collections.Hashtable, оскільки вони не мають переваги у дженериках. Ви можете використовувати словник TryGetValue або HasKey, якщо ви не знаєте, чи буде ключ існувати.
Крістіанп

Ну, а не HasKey, це має бути ContainsKey.
Крістіанп

-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.
снімарх

"Таким чином, ми можемо бути впевнені, що DictionaryBase використовує HashTable внутрішньо." - Це приємно, але це не має нічого спільного з питанням.
Джим Балтер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.