Java中的HashMap
是一种常用的数据结构,它基于哈希表(Hash Table)实现,用于存储键值对(key-value pairs)。HashMap
的底层实现涉及以下几个关键概念和机制:
-
哈希函数:
HashMap
通过哈希函数将键的哈希码(hash code)转换为数组的索引位置。哈希函数的目的是尽可能均匀地将键分布到数组的各个位置,以减少哈希冲突(hash collisions)。 -
数组和链表:
HashMap
内部使用一个数组(称为桶数组或bucket array
)来存储节点。每个数组位置(桶)中存放一个链表(或树结构,自Java 8起)用于处理哈希冲突。当两个键的哈希码相同时,它们会被存储到相同的桶中,此时通过链表来解决冲突。 -
节点(Node)类:
每个存储在HashMap
中的元素都被封装在一个节点对象中。节点对象包含键、值、哈希码以及指向下一个节点的引用。在Java 8及以后的版本中,如果链表长度超过一定阈值(默认是8),链表会转换为红黑树,以提高查找效率。 -
哈希冲突:
当两个不同的键通过哈希函数映射到相同的桶时,就会发生哈希冲突。HashMap
通过链表或红黑树来解决冲突。链表的插入时间复杂度为O(1),查找时间复杂度为O(n),而红黑树的查找时间复杂度为O(log n)。 -
扩容和再哈希:
HashMap
有一个负载因子(load factor),默认值为0.75。当HashMap
中的元素数量超过数组容量与负载因子的乘积时,HashMap
会进行扩容(resize),即将数组容量扩大为原来的两倍,并重新计算每个键的哈希值并重新分配到新的数组位置(再哈希rehash)。 -
插入、查找和删除操作:
- 插入(put):
HashMap
通过键的哈希值找到对应的桶,如果桶为空,则直接插入;如果桶不为空,则遍历链表,检查是否有相同的键,如果找到则更新值,否则将新节点插入链表末尾或红黑树中。 - 查找(get):通过键的哈希值找到对应的桶,然后遍历链表或红黑树,查找与该键匹配的节点,返回其值。
- 删除(remove):通过键的哈希值找到对应的桶,遍历链表或红黑树,找到并移除匹配的节点。
- 插入(put):
以下是简化的HashMap
节点类和关键方法的示例代码:
class Node<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;
}
}
class MyHashMap<K, V> {
static final int DEFAULT_INITIAL_CAPACITY = 16;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
Node<K, V>[] table;
int size;
int threshold;
final float loadFactor;
MyHashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
this.threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
this.table = (Node<K, V>[]) new Node[DEFAULT_INITIAL_CAPACITY];
}
int hash(Object key) {
return (key == null) ? 0 : (key.hashCode()) ^ (key.hashCode() >>> 16);
}
public V put(K key, V value) {
int hash = hash(key);
int i = (table.length - 1) & hash;
for (Node<K, V> e = table[i]; e != null; e = e.next) {
if (e.hash == hash && (e.key == key || (key != null && key.equals(e.key)))) {
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
Node<K, V> e = table[i];
table[i] = new Node<>(hash, key, value, e);
if (++size > threshold) resize();
return null;
}
public V get(Object key) {
int hash = hash(key);
int i = (table.length - 1) & hash;
for (Node<K, V> e = table[i]; e != null; e = e.next) {
if (e.hash == hash && (e.key == key || (key != null && key.equals(e.key)))) {
return e.value;
}
}
return null;
}
void resize() {
// Simplified resize method for demonstration purposes
int newCapacity = table.length * 2;
Node<K, V>[] newTable = (Node<K, V>[]) new Node[newCapacity];
for (int j = 0; j < table.length; j++) {
Node<K, V> e = table[j];
if (e != null) {
table[j] = null;
if (e.next == null) {
newTable[e.hash & (newCapacity - 1)] = e;
} else {
Node<K, V> loHead = null, loTail = null;
Node<K, V> hiHead = null, hiTail = null;
Node<K, V> next;
do {
next = e.next;
if ((e.hash & table.length) == 0) {
if (loTail == null) loHead = e;
else loTail.next = e;
loTail = e;
} else {
if (hiTail == null) hiHead = e;
else hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTable[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTable[j + table.length] = hiHead;
}
}
}
}
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
}
这个示例展示了HashMap
的基本工作原理,包括哈希函数的计算、链表的处理以及简单的扩容机制。在实际实现中,HashMap
还有许多优化和细节处理,以提高性能和稳定性。