1.是什么
HashMap是基于哈希表实现的,它存储键值对(key-value pairs)。以下是HashMap的主要实现原理:
1. 哈希桶数组(Buckets)
HashMap内部维护了一个叫做“哈希桶”的数组,数组的每个槽位对应一个桶,用于存放键值对。
2. 键的哈希值
当向HashMap中插入一个键值对时,首先会计算键的哈希值。这个哈希值决定了键值对应该存储在哪个桶中。
3. 索引计算
HashMap使用哈希值与数组长度的位运算来确定键值对在数组中的索引位置。例如,如果数组的长度是16,那么键的哈希值会与15(即16-1)进行位与操作(hash & (length - 1)),这样可以保证结果在0到15之间。
4. 处理哈希碰撞
如果两个不同的键产生了相同的索引,这称为哈希碰撞。HashMap通过链表来解决哈希碰撞。在Java 8及以后的版本中,当链表的长度超过一定阈值时,链表会被转换成红黑树,以优化搜索性能。
以下是HashMap插入操作的简化步骤:
public V put(K key, V value) {
int hash = hash(key); // 计算哈希值
int i = indexFor(hash, table.length); // 计算索引位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
addEntry(hash, key, value, i); // 添加新节点到链表或树中
return null;
}
HashMap的扩容机制
HashMap的扩容机制是当哈希桶数组中的元素达到一定的比例时,会进行扩容操作,以保持HashMap的性能。以下是扩容的主要步骤:
1. 装载因子(Load Factor)
装载因子是衡量HashMap满的程度的一个标准。它的默认值是0.75,这意味着当HashMap中的元素数量达到容量的75%时,就会触发扩容。
2. 扩容阈值
扩容阈值是HashMap能够容纳的最大元素数量。这个值等于数组的长度乘以装载因子。当HashMap中的元素数量达到这个阈值时,就会触发扩容。
3. 扩容操作
扩容操作包括以下步骤:
- 创建一个新的哈希桶数组,其大小是原数组大小的两倍。
- 遍历原数组中的所有元素,并将它们重新插入到新的数组中。因为数组的长度变了,所以每个元素的位置都需要重新计算。
以下是扩容操作的简化代码:
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable); // 重新计算所有元素在新数组中的位置
table = newTable;
threshold = (int)(newCapacity * loadFactor); // 更新扩容阈值
}
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity); // 重新计算索引位置
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
扩容是一个比较昂贵的操作,因为它需要重新计算所有元素的哈希值并将它们重新插入到新的数组中。因此,如果预先知道HashMap将存储大量元素,最好在创建HashMap时就指定一个足够大的初始容量,以减少扩容的次数。