目录
1 引言
2 TreeMap简介
2.1 定义
2.2 特点
2.3 适用场景
3 TreeMap的核心类和方法
3.1 Entry 类
3.2 核心方法
3.2.1 put(K key, V value) 方法
3.2.2 get(Object key) 方法
1. 查找节点
2. 返回结果
4 其他常用方法
5 总结
在编程的世界里,选择合适的数据结构对于解决问题至关重要。Java 提供了多种数据结构以满足不同的需求,其中之一便是 TreeMap
。作为一种特殊的 Map 实现,TreeMap
不仅能够存储键值对,还能根据键的自然顺序或者通过提供的比较器进行排序。这种特性使得 TreeMap
成为了处理需要有序数据访问场景的理想选择。
本文旨在为基础学习者提供一个清晰易懂的指南,介绍 TreeMap
中最常用的几种方法,以及如何利用它们来简化数据管理任务。我们将从最基本的构造方法开始,逐步讲解如何使用 TreeMap
来完成日常开发中的各种操作,并通过简单的示例帮助你更好地理解和应用这些知识。无论你是刚开始接触 Java 集合框架的新手,还是希望加深对 TreeMap
理解的开发者,本文都将为你打下坚实的基础。让我们一起进入 TreeMap
的世界,探索它为我们带来的便捷与强大功能吧。
TreeMap 是 Java 集合框架 (java.util 包) 中的一个类,用于存储键值对 (Key-Value Pair) 的集合。它是基于红黑树(Red-Black Tree)数据结构实现的,能够自动根据键的自然顺序或者通过提供的比较器(Comparator)对键进行排序。由于其底层使用了自平衡二叉搜索树,TreeMap 提供了高效的插入、删除和查找操作,时间复杂度为 O(log n)。
null
键:键不能为 null
,否则会抛出 NullPointerException
,但值可以为 null
。TreeMap
不是线程安全的,如果需要在多线程环境中使用,可以通过 Collections.synchronizedSortedMap
方法对其进行包装。TreeMap 中的每个元素被封装在一个 Entry
static final class Entry implements Map.Entry {
K key; // 键
V value; // 值
Entry left; // 左子节点
Entry right;// 右子节点
Entry parent; // 父节点
boolean color = BLACK; // 节点颜色(红色或黑色)
Entry(K key, V value, Entry parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
}
put 方法用于将一个键值对插入 TreeMap,并维护红黑树的平衡。
public V put(K key, V value) {
Entry t = root; // 从根节点开始
if (t == null) { // 如果树为空
compare(key, key); // 检查键的类型(可能为 null)
root = new Entry<>(key, value, null); // 创建一个新的根节点
size = 1; // 树的大小设置为 1
modCount++; // 修改计数器加 1
return null; // 返回 null,表示没有覆盖旧值
}
int cmp; // 比较结果
Entry parent; // 记录父节点
// 分离比较器和可比较路径
Comparator super K> cpr = comparator; // 获取比较器
if (cpr != null) { // 如果提供了自定义比较器
do {
parent = t; // 当前节点作为父节点
cmp = cpr.compare(key, t.key); // 使用比较器比较键
if (cmp < 0) // 如果新键小于当前节点的键
t = t.left; // 移动到左子树
else if (cmp > 0) // 如果新键大于当前节点的键
t = t.right; // 移动到右子树
else // 如果键相等
return t.setValue(value); // 更新值并返回旧值
} while (t != null); // 循环直到找到插入位置
}
else { // 如果没有提供比较器(使用自然顺序)
if (key == null) // 如果键为 null
throw new NullPointerException(); // 抛出空指针异常
@SuppressWarnings("unchecked")
Comparable super K> k = (Comparable super K>) key; // 将键转换为可比较类型
do {
parent = t; // 当前节点作为父节点
cmp = k.compareTo(t.key); // 使用 compareTo 比较键
if (cmp < 0) // 如果新键小于当前节点的键
t = t.left; // 移动到左子树
else if (cmp > 0) // 如果新键大于当前节点的键
t = t.right; // 移动到右子树
else // 如果键相等
return t.setValue(value); // 更新值并返回旧值
} while (t != null); // 循环直到找到插入位置
}
Entry e = new Entry<>(key, value, parent); // 创建新的节点
if (cmp < 0) // 如果新键小于父节点的键
parent.left = e; // 插入到左子树
else // 如果新键大于父节点的键
parent.right = e; // 插入到右子树
fixAfterInsertion(e); // 插入后调整红黑树的平衡
size++; // 树的大小加 1
modCount++; // 修改计数器加 1
return null; // 返回 null,表示没有覆盖旧值
}
流程概述:
1. 查找插入位置
从根节点开始,根据键的大小逐步向下查找,直到找到合适的插入位置。比较逻辑通过 Comparator
(自定义比较器)或键的自然顺序(实现 Comparable
接口)来实现。如果找到与新键相同的键,则直接更新对应的值;否则继续查找,直到到达叶子节点。
2. 插入新节点
在确定插入位置后,将新节点插入到红黑树中,并正确设置其与父节点的关系。如果新键小于父节点的键,则将其作为左子节点;否则作为右子节点。同时,新节点的初始颜色为红色,以便后续调整。
3. 维护红黑树平衡
插入新节点后,调用 fixAfterInsertion
方法对红黑树进行结构调整和重新着色,确保其满足红黑树的性质(如根节点为黑色、无相邻红色节点等),从而维持树的平衡性和高效性。
get 方法用于根据键查找对应的值。
public V get(Object key) {
Entry p = getEntry(key); // 调用 getEntry 方法查找键对应的节点
return (p == null ? null : p.value); // 如果找到节点,则返回其值;否则返回 null
}
final Entry getEntry(Object key) {
// 如果提供了比较器,则使用基于比较器的版本以提高性能
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null) // 如果键为 null,抛出空指针异常
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable super K> k = (Comparable super K>) key; // 将键转换为可比较类型
Entry p = root; // 从根节点开始查找
while (p != null) { // 循环遍历树
int cmp = k.compareTo(p.key); // 比较当前节点的键与目标键
if (cmp < 0)
p = p.left; // 如果目标键小于当前节点的键,向左子树移动
else if (cmp > 0)
p = p.right; // 如果目标键大于当前节点的键,向右子树移动
else
return p; // 如果相等,返回当前节点
}
return null; // 如果未找到对应节点,返回 null
}
流程概述:
从根节点开始,根据键的大小逐步向下查找目标节点。比较逻辑通过 Comparator
(自定义比较器)或键的自然顺序(实现 Comparable
接口)来完成。每次比较后,决定向左子树还是右子树移动,直到找到匹配的节点或到达叶子节点。
null
。remove(Object key)
containsKey(Object key)
判断是否包含指定键。
containsValue(Object value)
判断是否包含指定值。注意:此操作的时间复杂度为 O(n)。
firstKey()
返回当前最小的键。
lastKey()
返回当前最大的键。
firstEntry()
返回最小键对应的键值对。
lastEntry()
返回最大键对应的键值对。
keySet()
返回所有键的集合。
values()
返回所有值的集合。
entrySet()
返回所有键值对的集合。
clear()
移除所有映射关系。
size()
返回键值对的数量。
comparator()
null
(表示使用自然顺序)。所有操作代码整合:
import java.util.*;
public class TreeMapExample {
public static void main(String[] args) {
TreeMap treeMap = new TreeMap<>();
// 插入键值对
treeMap.put(3, "Alice");
treeMap.put(1, "Bob");
treeMap.put(2, "Charlie");
// 获取值
System.out.println("键 1 对应的值: " + treeMap.get(1));
// 删除键值对
treeMap.remove(2);
System.out.println("删除键 2 后的 TreeMap: " + treeMap);
// 判断是否存在
System.out.println("是否包含键 3: " + treeMap.containsKey(3));
System.out.println("是否包含值 'Alice': " + treeMap.containsValue("Alice"));
// 获取边界元素
System.out.println("最小键: " + treeMap.firstKey());
System.out.println("最大键: " + treeMap.lastKey());
// 遍历键值对
System.out.println("所有键: " + treeMap.keySet());
System.out.println("所有值: " + treeMap.values());
System.out.println("所有键值对: " + treeMap.entrySet());
// 清空并检查大小
treeMap.clear();
System.out.println("清空后大小: " + treeMap.size());
}
}
运行结果:
TreeMap 是 Java 集合框架中一个功能强大且高效的工具,适用于需要有序存储和访问键值对的场景。通过本文的介绍,我们从 TreeMap 的定义、特点、底层实现到核心方法的详细解析,逐步揭开了它的神秘面纱。希望本文能对你有所帮助!