一、HashMap的数据结构
HashMap是基于哈希表的Map接口的实现。它使用哈希表来存储键值对。这使得获取特定键的值变得非常快。每个键值对被存储在一个称为“桶”的结构中。桶的数量是固定的,但当HashMap增长时,这些桶会被重新分配到一个更大的数组中。
HashMap的内部结构包括数组和链表。当两个不同的键具有相同的哈希值时,它们会被存储在同一个桶中,但是作为链表的不同节点。
二、红黑树在HashMap中的应用
在Java 8中,HashMap的实现进行了一些优化,其中一个重要的改进是,当链表的长度超过一定的阈值(默认为8)时,链表会被转换为红黑树。这是为了提高在高哈希冲突情况下的性能。
红黑树是一种自平衡的二叉查找树,它保证了查找、插入和删除的时间复杂度为O(log n)。这意味着,即使在最坏的情况下,当多个键哈希到同一个桶时,其性能也会比链表好得多。
当红黑树的大小减少到一定的程度(默认为6)时,它会再次被转换回链表。
三、put方法流程
当我们调用put方法时,首先会计算键的hashCode()。这个哈希码然后被用来确定键值对应该存储在哪个桶中。如果该桶已经包含了一个或多个键值对,那么新的键值对会被添加到链表的末尾。如果键已经存在,则其对应的值会被新值替换。
源码分析:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
上述代码首先计算键的哈希值,然后调用putVal
方法来实际存储键值对。
四、HashMap扩容问题
当HashMap中的元素数量超过其容量的75%时,它会进行扩容。这是为了保持HashMap的性能,因为当桶的数量变得太少时,链表的长度会增加,这会降低查找的速度。扩容时,新的数组大小是原始大小的两倍,并且原始数据会被重新哈希到新的数组中。
五、HashMap死锁问题
在多线程环境下,如果多个线程同时修改HashMap,可能会导致死锁或数据不一致的问题。这是因为HashMap不是线程安全的。为了避免这种情况,可以使用ConcurrentHashMap,它是一个线程安全的版本,或者在修改时加锁。
六、HashMap面试知识点及解答
1.问:HashMap的默认初始容量和加载因子是多少?
答:默认初始容量是16,加载因子是0.75。
2.问:为什么HashMap不是线程安全的?
答:因为HashMap的put和get方法没有进行同步操作,所以在多线程环境下,可能会出现数据不一致的情况。
3.问:如何使HashMap变得线程安全?
答:可以使用Collections.synchronizedMap()方法来获取一个线程安全的HashMap,或者使用ConcurrentHashMap。
4.问:什么是哈希冲突,如何解决?
答:当两个不同的键具有相同的哈希值时,称为哈希冲突。HashMap通过链表和红黑树来解决哈希冲突。
5.问:为什么HashMap在链表长度超过8时转换为红黑树?
答:这是为了提高查找性能。链表的查找时间复杂度为O(n),而红黑树的查找时间复杂度为O(log n).