← Назад к вопросам
В каком случае вставка в HashMap будет выполняться с логарифмической сложностью
2.8 Senior🔥 91 комментариев
#JVM и память#Коллекции и структуры данных
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
В каком случае вставка в HashMap будет выполняться с логарифмической сложностью
HashMap в обычных случаях имеет сложность вставки O(1) в среднем. Однако вставка может стать O(log n) только в очень специфичном случае с Java 8+ и при определённых условиях коллизий.
Обычная сложность HashMap
val map = HashMap<String, Int>()
map["key1"] = 100 // O(1) в среднем
map["key2"] = 200 // O(1) в среднем
map["key3"] = 300 // O(1) в среднем
Когда вставка становится O(log n)?
Только в Java 8+: Когда происходит много коллизий (hash conflict), HashMap использует красно-чёрное дерево (TreeNode) вместо связного списка.
// Java 8+ особенность: порог для преобразования
// TREEIFY_THRESHOLD = 8 (если в бакете 8+ элементов)
// UNTREEIFY_THRESHOLD = 6 (если уменьшилось до 6)
val map = HashMap<String, Int>()
// Если создать объекты с одинаковым hash code:
class BadKey(val value: String) {
override fun hashCode() = 0 // Все объекты имеют одинаковый hash
override fun equals(other: Any?) = value == (other as? BadKey)?.value
}
val badMap = HashMap<BadKey, Int>()
for (i in 0..20) {
badMap[BadKey("key$i")] = i
// Первые 7 вставок: O(1) — используется связный список
// 8-я вставка: преобразование в красно-чёрное дерево
// С 9-й вставки: O(log n) — поиск в дереве
}
Java 8 HashMap архитектура
До коллизий (нормальный случай):
bucket[0] -> [key1 -> val1]
bucket[1] -> [key2 -> val2]
[key3 -> val3]
После 8+ коллизий (Java 8+):
bucket[1] -> TreeNode (красно-чёрное дерево)
/
key2
/ \
key3 key4
Пример с плохим hash code
data class UserKey(val id: Int) {
override fun hashCode(): Int = 1 // Все объекты коллизируют!
}
val map = HashMap<UserKey, String>()
// Вставка 1-7: O(1) — связный список
for (i in 1..7) {
map[UserKey(i)] = "User $i"
}
// Вставка 8: O(1) -> преобразование в дерево
map[UserKey(8)] = "User 8"
// Вставка 9+: O(log n) — операции в красно-чёрном дереве
for (i in 9..100) {
map[UserKey(i)] = "User $i" // O(log n) из-за поиска в дереве
}
Почему красно-чёрное дерево в HashMap (Java 8+)?
- Защита от DoS атак — злоумышленник не может умышленно вызвать O(n^2)
- Гарантированная сложность — O(log n) вместо O(n) при коллизиях
- Лучше всего работает на практике
TreeMap: всегда O(log n)
// TreeMap ВСЕГДА использует красно-чёрное дерево
val treeMap = TreeMap<String, Int>()
for (i in 1..100) {
treeMap["key$i"] = i // ВСЕГДА O(log n)
}
// TreeMap требует Comparable (или Comparator)
val treeMap2 = TreeMap<Int, String>()
treeMap2[5] = "Five" // O(log 1)
treeMap2[3] = "Three" // O(log 2)
treeMap2[7] = "Seven" // O(log 3)
На практике в Android
// Обычно разработчик не сталкивается с этим
val userCache = HashMap<Int, User>() // O(1) всегда
for (user in users) {
userCache[user.id] = user
}
// Плохой hash code может испортить производительность
data class BadUser(val id: Int) {
override fun hashCode() = 0 // Опасно!
}
// Хороший hash code
data class GoodUser(val id: Int, val name: String) {
override fun hashCode() = 31 * id.hashCode() + name.hashCode()
}
Выводы
HashMap:
- O(1) средняя сложность (обычно)
- O(log n) при коллизиях (Java 8+, когда бакет использует TreeNode)
- O(n) худший случай (практически невозможен в Java 8+)
TreeMap:
- O(log n) всегда (красно-чёрное дерево)
- Требует Comparable элементов
- Медленнее HashMap при нормальных операциях
Вывод: В нормальных условиях вставка в HashMap — это O(1). O(log n) возникает только при плохом hash code и в Java 8+, когда происходит деградация коллизий из связного списка в дерево.