Вопрос 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 внутри]]