HashMap
是 Java 调集结构中的一个常用类,完结了依据哈希表的键值对存储和检索功用。它供应了高效的插入、查找和删去操作,并且在大多数场景下具有常数时间复杂度(O(1))。
本文主要对HashMap中一些中心概念进行解说和阐明,其他基本概念能够先参看这篇文章:java根底_吃透Java调集系列九:HashMap
要害概念
哈希码(Hash Code)
在HashMap
中,每个键都有一个对应的哈希码,它是依据键的hashCode()
方法生成的。哈希码用于承认键值对在哈希表中的存储方位。
比方其间get
方法,是通过Key的Hash方法去找其间的bucket的
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
hash方法也不是朴素的key的hashCode,详细如下
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
通过getNode能够了解到hashMap的数据结构是hash数组 链表 红黑树(JDK8下)
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
哈希表(Hash Table)
HashMap
内部运用哈希表数据结构来存储键值对。哈希表是一个数组,每个元素称为桶(Bucket),通过哈希码将键值对映射到桶的索引方位。哈希表供应了快速的存储和检索操作。
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
桶(Bucket)
哈希表中的每个元素称为桶,它能够存储一个或多个键值对。当多个键值对的哈希码相一起,它们会被存储在同一个桶中,构成一个链表或红黑树结构。
对应Node
,默许是单向链接数据结构
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key "=" value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
负载因子(Load Factor)
HashMap
运用负载因子来操控哈希表的填充程度。负载因子是指其时元素数量与哈希表容量的比值,当元素数量逾越负载因子和其时容量的乘积时,会触发扩容操作。
链表(Linked List)和红黑树(Red-Black Tree)
在 JDK 8 中的HashMap
完结中,当链表长度逾越阈值时,会将链表转化为红黑树,然后提高在链表较长时的查找效率。
-
链表转化为红黑树:
- 当链表长度到达
TREEIFY_THRESHOLD
(默许为 8)时,会将链表转化为红黑树。 - 转化过程中,首要会创立一个新的红黑树节点作为根节点,并将链表中的元素逐个增加到红黑树中,坚持相对次第不变。
- 在增加过程中,假如发现链表中的元素现已存在于红黑树中,则将链表元素的值更新到红黑树节点中。
- 转化完结后,本来的链表将被丢掉,以节约内存。
- 当链表长度到达
-
红黑树转化为链表:
- 当红黑树中的节点数量削减到
UNTREEIFY_THRESHOLD
(默许为 6)以下时,会将红黑树转化回链表。 - 转化过程中,首要会将红黑树中的节点逐个遍历,并按照相对次第增加到链表中。
- 转化完结后,本来的红黑树将被丢掉,以节约内存。
- 当红黑树中的节点数量削减到
equals 方法和 hashCode 方法
为了正确存储和检索键值对,键的类必须正确完结equals
和hashCode
方法。equals
方法用于判别键是否相等,hashCode
方法用于生成键的哈希码。