Питання 26 · Розділ 10

В чому різниця між HashMap та Hashtable?

Всі методи синхронізовані:

Мовні версії: English Russian Ukrainian

🟢 Junior Level

HashMap і Hashtable — обидві зберігають дані у форматі “ключ-значення”, але це дуже різні класи.

HashMap Hashtable
Швидка Повільна
Не потокобезпечна Потокобезпечна
Дозволяє null Забороняє null
Сучасна (Java 1.2) Застаріла (Java 1.0)

Приклад:

// HashMap — сучасний вибір
Map<String, Integer> map = new HashMap<>();
map.put(null, 1);     // OK
map.put("key", null); // OK

// Hashtable — застаріла, не використовуйте
Map<String, Integer> table = new Hashtable<>();
table.put(null, 1);     // NullPointerException!
table.put("key", null); // NullPointerException!

Головна порада: Завжди використовуйте HashMap (або ConcurrentHashMap для багатопотоковості). Hashtable — це legacy.

🟡 Middle Level

Детальне порівняння

Характеристика Hashtable HashMap
Синхронізація Так (кожен метод) Ні
Null ключі Ні (NPE) Так (1)
Null значення Ні (NPE) Так (багато)
Наслідування Dictionary AbstractMap
Версія Java JDK 1.0 JDK 1.2
Індексація hash % n (n-1) & hash
Ітератор Enumeration + Iterator Iterator (fail-fast)

Чому Hashtable повільна?

Всі методи синхронізовані:

public synchronized V put(K key, V value) { ... }
public synchronized V get(Object key) { ... }

Навіть для читання — блокування. В багатопотоковому середовищі всі потоки вишиковуються в чергу.

Алгоритм індексації

HashMap: (n-1) & hash — швидка бітова маска (вимагає степінь двійки) Hashtable: (hash & 0x7FFFFFFF) % n — ділення за модулем (повільніше, але працює з будь-яким розміром)

HashMap додатково перемішує хеш через spread-функцію (XOR старших і молодших 16 біт) для кращого розподілу. Hashtable бере hashCode() напряму.

Коли Hashtable ще зустрічається?

  • Legacy-код (проєкти старше 15-20 років)
  • Питання на співбесідах 😊
  • Підручники, які не оновлювалися

Чим замінити?

Потрібна Замість Hashtable використовуйте
Просто Map HashMap
Потокобезпечність ConcurrentHashMap
Синхронізована Map Collections.synchronizedMap(new HashMap<>())

Типові помилки

  1. Використання Hashtable в новому коді — це anti-pattern
  2. Думати, що Hashtable = швидка ConcurrentHashMap — ні, вона повільніша
  3. Enumeration vs Iterator — Hashtable повертає Enumeration (без remove, без fail-fast)

🔴 Senior Level

Internal: Synchronization Overhead

Кожен synchronized метод — це monitor enter/exit:

// Hashtable.put:
public synchronized V put(K key, V value) {
    // monitor enter
    // ... logic
    // monitor exit
}

В багатопотоковому середовищі:

  • Contention на одному моніторі
  • Thread parking/unparking
  • Memory barrier при кожному вході/виході

ConcurrentHashMap вирішує це через CAS + granular locking.

Historical Context

  • Dictionary (1995) — абстрактний предок Hashtable, deprecated
  • Hashtable (1995) — адаптована під Map в Java 1.2
  • HashMap (1998) — створена з нуля для Collections Framework

Hashtable зберегли лише для backward compatibility.

Indexing: Modulo vs Bitwise AND

// Hashtable:
int index = (hash & 0x7FFFFFFF) % table.length;
// & 0x7FFFFFFF — прибирає знаковий біт (hash може бути від'ємним)
// % — ділення за модулем, дорога операція

// HashMap:
int index = (n - 1) & hash;
// Лише при n = 2^k
// & — побітове І, 1 такт

Hashtable може використовувати прості числа для розміру таблиці (кращий розподіл), але modulo дорожче.

Thread Safety: Hashtable vs ConcurrentHashMap

Метрика Hashtable ConcurrentHashMap
Read (1 thread) ~15 ns ~8 ns
Read (8 threads) ~500 ns ~60 ns
Write (1 thread) ~20 ns ~15 ns
Write (8 threads) ~2000 ns ~120 ns

Hashtable програє в 10-30 разів на багатопотокових операціях.

Why Hashtable is Still in JDK

  • Backward compatibility (мільйони рядків legacy-коду)
  • Деякі enterprise-фреймворки залежать від неї
  • Видалення зламало б код (навіть якщо він поганий)

Modern Best Practice

// Ніколи:
Map<K, V> map = new Hashtable<>();

// Замість цього:
Map<K, V> map = new HashMap<>();                        // Single-thread
Map<K, V> map = new ConcurrentHashMap<>();              // Multi-thread
Map<K, V> map = Collections.synchronizedMap(new HashMap<>()); // Якщо потрібна повна синхронізація

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • Hashtable — legacy (Java 1.0), HashMap — сучасний вибір (Java 1.2)
  • Hashtable синхронізована (кожен метод) → повільна, HashMap — ні
  • Hashtable забороняє null, HashMap допускає null-ключ і null-значення
  • Індексація: Hashtable = (hash & 0x7FFFFFFF) % n, HashMap = (n-1) & hash
  • HashMap використовує spread-функцію (XOR старших/молодших 16 біт), Hashtable бере hashCode() напряму
  • Hashtable програє ConcurrentHashMap в 10-30 разів на багатопотокових операціях
  • Hashtable залишена лише для backward compatibility

Часті уточнюючі запитання:

  • Коли використовувати Hashtable? — ніколи в новому коді; тільки legacy support
  • Чому Hashtable повільніша за ConcurrentHashMap? — один монітор на все vs granular locking + CAS
  • Чим відрізняється Enumeration від Iterator? — Enumeration без remove() і fail-fast
  • Чому HashMap швидша? — бітова маска & vs ділення %, немає синхронізації

Червоні прапорці (НЕ говорити):

  • «Hashtable = швидка потокобезпечна Map» — ні, повільніша за ConcurrentHashMap
  • «HashMap використовує ділення для індексації» — ні, бітову маску
  • «Можна використовувати Hashtable в новому коді» — ні, це anti-pattern

Пов’язані теми:

  • [[23. Що таке ConcurrentHashMap і чим він відрізняється від HashMap]]
  • [[22. Як HashMap працює в багатопотоковому середовищі]]
  • [[1. Як влаштований HashMap всередині]]