1. 简介
HashMap是Java中常用的一种数据结构,它是基于哈希表实现的。在Java的集合框架中,HashMap被广泛应用于各种场景中,例如缓存、数据库连接池、DNS解析等。它具有快速的查找、插入、删除等操作,而且在绝大部分场景中,HashMap的性能表现非常优秀。
2. 哈希表的内部实现
2.1 哈希函数
哈希函数是将不同的数据映射成不同的哈希值,而哈希值通常是一个整数。一个好的哈希函数应该能够均匀地将不同的数据映射成不同的哈希值,从而尽可能地减少哈希冲突的发生。
在HashMap中,我们可以通过对Key进行哈希运算得到其对应的哈希值,然后根据哈希值和一些管理指针的帮助来查找到Key对应的Value。Java中的HashMap使用了一种称为“拉链法”(Separate Chaining)的方式来解决哈希冲突,即将多个具有相同哈希值的Key-Value对存储在同一个链表中。
2.2 实现方式
在Java 8中,HashMap的内部是由数组和链表共同组成的。数组负责存储链表头节点,而链表负责存储哈希冲突的Key-Value对。具体而言,数组中每一个元素都是一个链表的头节点,而链表中的每一个节点则是一个Key-Value对。
当HashMap中的元素数量达到一定阈值后,就需要进行扩容。扩容是将数组容量扩大一倍,并将所有的元素重新插入新的数组中。这个过程是比较耗时的,但是它是非常必要的,因为随着元素数量的增加,哈希冲突的概率也会越来越大,如果数组过于拥挤,那么链表的长度就会变得非常长,导致搜索效率下降。
3. 插入操作
HashMap中的插入操作是通过以下几个步骤完成的:
调用哈希函数得到Key对应的哈希值
根据哈希值找到Key对应的数组下标。
在数组中找到对应的链表头节点,遍历链表,查找是否已经存在相同的Key。如果存在,更新Value的值。如果不存在,将新的Key-Value对插入到链表头部。
如果插入完成后链表中元素的数量超过了阈值,触发扩容操作。扩容后需要重新计算所有元素的哈希值,并重新放置到数组中。
下面是HashMap中put方法的源代码:
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry 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;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
在该方法中,首先会进行一些边界条件的判断,然后通过hash方法计算出Key对应的哈希值,并通过indexFor方法计算出应该将这个Key存储在数组的哪一个位置。然后,我们需要遍历该位置处的链表,查找是否已经存在相同的Key。如果已经存在,就更新Value的值,否则将新的Key-Value对加入到链表头部,并考虑是否需要进行扩容操作。需要注意的是,在这个方法中,所有的链表都是通过数组中的Entry对象来表示的。
4. 查找操作
HashMap中的查找操作是通过以下几个步骤完成的:
调用哈希函数得到Key对应的哈希值
根据哈希值找到Key对应的数组下标
在数组中找到对应的链表头节点,遍历链表,查找是否存在相同的Key
下面是HashMap中get方法的源代码:
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key);
for (Entry e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
在该方法中,首先对Key进行了一些特殊情况的处理,然后调用hash方法得到Key对应的哈希值,并调用indexFor方法得到Key在数组中的位置。然后遍历该位置处的链表,查找是否存在相同的Key。如果查找到了,就返回对应的Entry对象,否则返回null。
5. 总结
在Java中,HashMap是一种非常实用的数据结构,它能够帮助我们在很多情况下快速地进行查找、插入、删除等操作。HashMap的内部是由数组和链表组合而成,通过哈希函数将Key转换成哈希值,再通过数组和链表来进行查找、插入、删除等操作。需要注意的是,HashMap可能会出现哈希冲突的情况,因此在插入和查找时需要进行特殊处理。另外,如果在实际应用中需要对HashMap进行高并发的操作,还需要考虑线程安全等问题。