В чём разница между 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 внутри]]