前言
HashMap、LinkedHashMap 和 TreeMap 都是 Java 集合中用于存储键值对的 Map 接口的实现类,但它们在底层数据结构、元素顺序、性能以及使用场景等方面存在明显的区别,以下为各个方面的区别:
底层数据结构
- HashMap:JDK 8 及以后的版本中,HashMap底层采用数组 + 链表 + 红黑树的数据结构。数组作为哈希表的基础存储结构,每个数组元素称为一个桶(bucket)。当发生哈希冲突时,会将相同哈希值的元素以链表的形式存储在桶中。当链表长度超过 8 且数组长度大于 64 时,链表会转换为红黑树,以提高查找效率。
- LinkedHashMap:继承自HashMap,它在 HashMap的基础上增加了一个双向链表,用于维护元素的插入顺序或访问顺序。这个双向链表将所有的键值对连接起来,使得元素可以按照特定的顺序进行遍历。
- TreeMap:底层使用红黑树(一种自平衡的二叉搜索树)来存储键值对。红黑树的每个节点包含一个键值对,并且根据键的自然顺序或者指定的比较器顺序进行排序。
元素顺序
- HashMap:不保证元素的插入顺序,也不保证元素的顺序会随着时间的推移保持不变。在进行遍历操作时,元素的顺序是不确定的。
- LinkedHashMap:可以维护元素的插入顺序,即按照元素插入的先后顺序进行遍历。此外,它还可以通过构造函数指定按照访问顺序(access - order)进行排序,最近访问的元素会被移动到链表的尾部。
- TreeMap:会根据键的自然顺序或者指定的比较器顺序对元素进行排序。在进行遍历操作时,元素会按照键的排序顺序依次被访问。
性能
- HashMap:插入、查找和删除操作的平均时间复杂度为O(1),但在最坏情况下(所有元素都存储在同一个链表中),时间复杂度会退化为O(n) 。由于 HashMap 不需要维护元素的顺序,因此在不考虑元素顺序的场景下,它的性能是最好的。
- LinkedHashMap:插入、查找和删除操作的时间复杂度同样为O(1),因为它继承自 HashMap,底层的哈希表操作性能与 HashMap 相同。但由于需要维护双向链表,会额外消耗一些空间和时间用于链表的维护。
- TreeMap:插入、查找和删除操作的时间复杂度为O(log n),因为红黑树的插入、查找和删除操作都需要进行树的平衡调整,以保证树的高度始终保持在O(log n)级别。相比于 HashMap 和 LinkedHashMap,TreeMap 的性能相对较低。
使用场景
- HashMap:适用于不需要维护元素顺序,只需要快速进行插入、查找和删除操作的场景。例如,缓存系统、数据统计等。
- LinkedHashMap:适用于需要维护元素插入顺序或者访问顺序的场景。例如,实现 LRU(Least Recently Used,最近最少使用)缓存时,可以使用 LinkedHashMap 并将其访问顺序设置为 true,这样当缓存满时,最近最少访问的元素会自动被移除。
- TreeMap:适用于需要根据键的排序顺序进行遍历或者查找的场景。例如,对数据进行排序统计、范围查找等。
代码示例
public static void main(String[] args) {
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("cz", 3);
hashMap.put("ae", 1);
hashMap.put("br", 2);
System.out.println("HashMap: " + hashMap);
LinkedHashMap<String, Integer> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("cz", 3);
linkedHashMap.put("ae", 1);
linkedHashMap.put("br", 2);
System.out.println("LinkedHashMap: " + linkedHashMap);
TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("cz", 3);
treeMap.put("ae", 1);
treeMap.put("br", 2);
System.out.println("TreeMap: " + treeMap);
System.out.println("hashMap");
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
System.out.println("linkedHashMap");
for (Map.Entry<String, Integer> entry : linkedHashMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
System.out.println("TreeMap");
for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
总结
|-- HashMap、LinkedHashMap、TreeMap的区别
| |-- 底层数据结构
| | |-- HashMap
| | | |-- 数组 + 链表 + 红黑树
| | | |-- 哈希冲突存链表,长超8且数组长超64转红黑树
| | |-- LinkedHashMap
| | | |-- 继承HashMap
| | | |-- 增加双向链表维护顺序
| | |-- TreeMap
| | |-- 红黑树
| | |-- 按键排序存键值对
| |-- 元素顺序
| | |-- HashMap
| | | |-- 不保证插入顺序
| | | |-- 遍历顺序不确定
| | |-- LinkedHashMap
| | | |-- 可维护插入顺序
| | | |-- 可按访问顺序排序
| | |-- TreeMap
| | |-- 按键自然或指定比较器顺序排序
| | |-- 遍历按键排序顺序
| |-- 性能
| | |-- HashMap
| | | |-- 平均O(1),最坏O(n)
| | | |-- 不考虑顺序性能最佳
| | |-- LinkedHashMap
| | | |-- O(1)
| | | |-- 维护链表有额外开销
| | |-- TreeMap
| | |-- O(log n)
| | |-- 性能相对低
| |-- 使用场景
| | |-- HashMap
| | | |-- 不维护顺序,快速操作
| | | |-- 缓存、统计等
| | |-- LinkedHashMap
| | | |-- 维护插入或访问顺序
| | | |-- LRU缓存等
| | |-- TreeMap
| | |-- 按键排序遍历或查找
| | |-- 排序统计、范围查找等