В чем разница между HashMap и Hashtable в Java?
Основные различия между HashMap и Hashtable
Давайте разберем ключевые отличия, которые помогут вам понять, почему HashMap — это современный стандарт, а Hashtable остался в прошлом.
1. Синхронизация и потокобезопасность
Самое главное отличие: Hashtable синхронизирован, а HashMap — нет. Это означает, что методы Hashtable (put, get, remove и т.д.) синхронизированы на уровне класса, что делает его потокобезопасным, но медленным в однопоточных сценариях. HashMap быстрее, но требует внешней синхронизации при работе из нескольких потоков.
// Hashtable — потокобезопасен по умолчанию
Hashtable<String, String> table = new Hashtable<>();
table.put("key", "value"); // synchronized
// HashMap — не синхронизирован
HashMap<String, String> map = new HashMap<>();
map.put("key", "value"); // не synchronized
// Для потокобезопасного HashMap используйте:
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());
// Или лучше ConcurrentHashMap:
Map<String, String> concurrentMap = new ConcurrentHashMap<>();
2. null-ключи и null-значения
HashMap разрешает один null-ключ и любое количество null-значений. Hashtable не разрешает null ни для ключей, ни для значений — при попытке вставить null вы получите NullPointerException.
HashMap<String, String> map = new HashMap<>();
map.put(null, "value1"); // OK
map.put("key", null); // OK
System.out.println(map.get(null)); // "value1"
Hashtable<String, String> table = new Hashtable<>();
// table.put(null, "value"); // NullPointerException!
// table.put("key", null); // NullPointerException!
3. Производительность
В однопоточных приложениях HashMap работает значительно быстрее Hashtable из-за отсутствия накладных расходов на синхронизацию. Hashtable синхронизирует каждый метод, что замедляет работу. Если вам нужна потокобезопасная карта с высокой производительностью, используйте ConcurrentHashMap — он использует сегментированную блокировку и гораздо эффективнее Hashtable.
4. Итерация и Enumeration
HashMap использует Iterator, который является fail-fast — если карта изменяется после создания итератора (кроме через сам итератор), выбрасывается ConcurrentModificationException. Hashtable поддерживает как Iterator (fail-fast), так и устаревший Enumeration (не fail-fast).
// Iterator для HashMap
HashMap<String, String> map = new HashMap<>();
map.put("a", "1");
Iterator<Map.Entry<String, String>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, String> entry = iter.next();
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// Enumeration для Hashtable (устаревший способ)
Hashtable<String, String> table = new Hashtable<>();
table.put("a", "1");
Enumeration<String> keys = table.keys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
System.out.println(key + ": " + table.get(key));
}
5. Наследование и история
Hashtable — это класс из Java 1.0 (1996 год), он наследуется от Dictionary (устаревший абстрактный класс). HashMap появился в Java 1.2 (1998 год) как часть Collections Framework, наследуется от AbstractMap и реализует интерфейс Map. HashMap — это современная, хорошо продуманная реализация.
6. Емкость и коэффициент загрузки
Оба класса используют понятие "емкость" (capacity) и "коэффициент загрузки" (load factor), но есть нюансы:
- Начальная емкость HashMap по умолчанию — 16, Hashtable — 11.
- HashMap использует степень двойки для размера (16, 32, 64...), что оптимизирует хеширование.
- Hashtable использует простые числа (11, 23, 47...).
- Коэффициент загрузки по умолчанию у обоих — 0.75.
Что выбрать?
Используйте HashMap для однопоточных приложений — это быстрее и современнее. Для многопоточных сценариев используйте ConcurrentHashMap (начиная с Java 5). Hashtable считается устаревшим (legacy) классом, и его не рекомендуется использовать в новом коде. Если вы видите Hashtable в чужом коде, это знак того, что код написан до 1998 года или не обновлялся.
<<Дополнительные соображения: когда Hashtable всё ещё может быть полезен
Хотя Hashtable считается устаревшим, есть несколько ситуаций, где его можно встретить:
- Legacy-код: В старых проектах, написанных до Java 5, может использоваться Hashtable. Переписывать его на HashMap не всегда оправдано, если код работает стабильно.
- Сериализация: Hashtable имеет встроенную поддержку сериализации через интерфейс Serializable, что может быть удобно в некоторых сценариях.
- Синхронизация по умолчанию: Если вам нужно простое потокобезопасное хранилище без дополнительных зависимостей, Hashtable может быть быстрым решением (хотя ConcurrentHashMap всё равно лучше).
Однако помните: в современной Java почти всегда лучше использовать HashMap (для однопоточного доступа) или ConcurrentHashMap (для многопоточного). Hashtable не поддерживает новые возможности, такие как Stream API, и его производительность уступает альтернативам. Если вы поддерживаете старый код с Hashtable, рассмотрите возможность миграции на ConcurrentHashMap для улучшения производительности и совместимости.