HashMap
是 Java 中最常用的集合类之一,用于存储键值对。在 JDK 1.7 和 JDK 1.8 中,HashMap
的实现有一些重要的改进和优化。以下是它们的主要区别和底层实现的详细说明。
1. HashMap
的底层实现:
HashMap
的底层实现是基于 哈希表 的,它通过哈希算法将键值对存储在一个数组中。具体实现包括以下几个关键点:
(1)数据结构:
- JDK 1.7:
HashMap
使用 数组 + 链表 的结构。- 数组中的每个元素是一个
Entry
对象,Entry
是一个单向链表节点,包含键、值、哈希值和指向下一个节点的指针。 - 当发生哈希冲突时,新的元素会被添加到链表的头部(头插法)。
- 数组中的每个元素是一个
- JDK 1.8:
HashMap
使用 数组 + 链表 + 红黑树 的结构。- 数组中的每个元素是一个
Node
对象,Node
是一个单向链表节点。 - 当链表的长度超过阈值(默认是 8)时,链表会转换为红黑树,以提高查找效率。
- 当红黑树的节点数小于阈值(默认是 6)时,红黑树会退化为链表。
- 数组中的每个元素是一个
(2)哈希冲突解决:
- JDK 1.7:使用链表解决哈希冲突,新元素插入链表的头部(头插法)。
- JDK 1.8:使用链表和红黑树解决哈希冲突,新元素插入链表的尾部(尾插法)。
(3)扩容机制:
- JDK 1.7:扩容时,会重新计算每个元素的哈希值,并将其放入新的数组中。这个过程可能会导致链表逆序,从而在多线程环境下产生死循环。
- JDK 1.8:扩容时,通过高位运算确定元素在新数组中的位置,避免了链表逆序的问题。
2. JDK 1.7 和 JDK 1.8 的主要区别:
特性 | JDK 1.7 | JDK 1.8 |
---|---|---|
数据结构 | 数组 + 链表 | 数组 + 链表 + 红黑树 |
哈希冲突解决 | 链表(头插法) | 链表(尾插法) + 红黑树 |
链表转红黑树阈值 | 无 | 链表长度 > 8 时转换为红黑树 |
红黑树退化为链表阈值 | 无 | 红黑树节点数 < 6 时退化为链表 |
扩容机制 | 重新计算哈希值,可能导致死循环 | 高位运算,避免链表逆序 |
性能 | 链表过长时查找效率低 | 红黑树优化了查找效率 |
3. HashMap
的核心方法:
(1)put()
方法:
- 计算键的哈希值,确定存储位置。
- 如果发生哈希冲突:
- JDK 1.7:将新元素插入链表头部。
- JDK 1.8:将新元素插入链表尾部,如果链表长度超过阈值,转换为红黑树。
- 如果键已经存在,则更新值。
(2)get()
方法:
- 计算键的哈希值,确定存储位置。
- 如果发生哈希冲突:
- JDK 1.7:遍历链表查找元素。
- JDK 1.8:如果是链表,遍历链表查找;如果是红黑树,使用红黑树查找。
(3)resize()
方法:
- 当
HashMap
的元素数量超过容量 × 负载因子时,触发扩容。 - JDK 1.7:重新计算每个元素的哈希值,可能导致死循环。
- JDK 1.8:通过高位运算确定元素在新数组中的位置,避免链表逆序。
4. 示例代码:
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("orange", 3);
System.out.println(map.get("banana")); // 输出: 2
}
}
5. 总结:
- JDK 1.7:
HashMap
使用数组 + 链表的结构,链表过长时查找效率低,且扩容时可能导致死循环。 - JDK 1.8:
HashMap
使用数组 + 链表 + 红黑树的结构,优化了查找效率,避免了扩容时的死循环问题。 HashMap
的核心机制是通过哈希算法确定存储位置,并通过链表或红黑树解决哈希冲突。
6. 扩展:
- 线程安全问题:
HashMap
是非线程安全的,多线程环境下可以使用ConcurrentHashMap
。 - 负载因子:
HashMap
的默认负载因子是 0.75,表示当元素数量达到容量的 75% 时触发扩容。 - 初始容量:
HashMap
的默认初始容量是 16,扩容时容量变为原来的 2 倍。
THE END
暂无评论内容