В чому різниця між HashMap та Hashtable?
Всі методи синхронізовані:
🟢 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<>()) |
Типові помилки
- Використання Hashtable в новому коді — це anti-pattern
- Думати, що Hashtable = швидка ConcurrentHashMap — ні, вона повільніша
- 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 всередині]]