054_TreeMap / LinkedHashMap
一、概述
TreeMap和LinkedHashMap均是Java集合框架中Map接口的重要实现类,在保留Map接口“键值对映射”核心特性的基础上,分别提供了排序能力和顺序保留能力,适用于不同的业务场景。
- TreeMap:基于红黑树实现,支持键的排序(自然排序或定制排序),元素按键的顺序存储和访问。
- LinkedHashMap:基于哈希表+双向链表实现,保留键值对的插入顺序或访问顺序,兼具哈希表的高效查询和链表的有序性。
二、TreeMap详解
2.1 底层结构与核心原理
TreeMap底层基于红黑树(一种自平衡二叉搜索树) 实现,其核心原理是通过红黑树的特性保证键的有序性:
- 红黑树的每个节点存储一个键值对(Entry<K,V>),键的大小关系决定节点在树中的位置。
- 左子树的所有节点键值小于父节点键值,右子树的所有节点键值大于父节点键值(二叉搜索树特性)。
- 通过红黑树的自平衡机制(旋转和变色),保证树的高度始终为O(log n),确保增删改查的高效性。
2.2 核心特性
特点 | 说明 |
---|---|
有序性 | 元素按键的排序规则存储(非插入顺序),默认升序,可通过比较器定制排序方向。 |
键唯一性 | 依赖键的比较结果去重:若两键通过compareTo()(或compare())返回0,则视为重复键,新值覆盖旧值。 |
键可比较性 | 键必须实现Comparable接口(自然排序)或通过构造器传入Comparator(定制排序),否则插入时抛出ClassCastException。 |
无null键 | 不允许存储null键(排序时无法比较null),但允许null值。 |
线程不安全 | 非同步实现,多线程并发修改需额外同步(如Collections.synchronizedMap())。 |
性能 | 增删改查时间复杂度均为O(log n)(红黑树平衡操作开销)。 |
2.3 排序方式
- 自然排序(默认)
键所属类实现Comparable接口,重写compareTo(T o)方法定义排序规则:
// 自定义键类型(按年龄升序排序)
class User implements Comparable<User> {private String name;private int age;public User(String name, int age) {this.name = name;this.age = age;}// 按年龄升序排序,年龄相同则按姓名排序@Overridepublic int compareTo(User o) {if (this.age != o.age) {return this.age - o.age;}return this.name.compareTo(o.name);}
}
- 定制排序(外部比较器)
创建TreeMap时传入Comparator接口实现类,自定义排序规则(无需键类实现Comparable):
// 定制排序:按User的年龄降序
Comparator<User> ageDescComparator = (u1, u2) -> u2.age - u1.age;
Map<User, String> treeMap = new TreeMap<>(ageDescComparator);
2.4 常用方法与示例
核心方法:
方法定义 | 功能说明 |
---|---|
V put(K key, V value) | 插入键值对,按排序规则存储,键重复则覆盖旧值。 |
V get(Object key) | 获取指定键对应的值,不存在则返回null。 |
K firstKey() | 返回最小的键(按排序规则)。 |
K lastKey() | 返回最大的键(按排序规则)。 |
K ceilingKey(K key) | 返回大于等于key的最小键,不存在则返回null。 |
K floorKey(K key) | 返回小于等于key的最大键,不存在则返回null。 |
NavigableSet keySet() | 返回键的有序集合,支持导航操作(如descendingSet()逆序遍历)。 |
示例代码:
import java.util.TreeMap;public class TreeMapDemo {public static void main(String[] args) {// 自然排序:按String的字典序(升序)TreeMap<String, Integer> treeMap = new TreeMap<>();treeMap.put("Banana", 2);treeMap.put("Apple", 1);treeMap.put("Cherry", 3);// 遍历:按键的升序输出System.out.println("键有序遍历:" + treeMap.keySet()); // [Apple, Banana, Cherry]// 导航方法System.out.println("最小键:" + treeMap.firstKey()); // AppleSystem.out.println("大于等于'B'的最小键:" + treeMap.ceilingKey("B")); // BananaSystem.out.println("小于等于'B'的最大键:" + treeMap.floorKey("B")); // Banana}
}
2.5 适用场景
- 需要按键排序的场景(如排行榜、字典序查询、区间统计)。
- 需导航操作的场景(如获取小于/大于指定键的元素、逆序遍历)。
- 键具有自然排序或可通过比较器定义排序规则的场景。
三、LinkedHashMap详解
3.1 底层结构与核心原理
LinkedHashMap底层基于哈希表(HashMap)+双向链表实现,继承自HashMap,在哈希表的基础上通过双向链表维护键值对的顺序,核心原理是:
- 哈希表:保证键的唯一性和高效查询(同HashMap,通过hashCode()和equals()去重)。
- 双向链表:每个节点包含before和after指针,记录节点的前驱和后继,维护键值对的顺序(插入顺序或访问顺序)。
LinkedHashMap通过accessOrder参数(默认false)控制顺序类型:
- accessOrder = false(默认):按插入顺序排序(插入早的节点在链表前)。
- accessOrder = true:按访问顺序排序(最近访问的节点移至链表尾部,可实现LRU缓存)。
3.2 核心特性
特点 | 说明 |
---|---|
有序性 | 按插入顺序(默认)或访问顺序(accessOrder = true)存储和遍历。 |
键唯一性 | 同HashMap,依赖hashCode()和equals()保证键的唯一性。 |
查询性能 | 同哈希表特性,get()和containsKey()时间复杂度为O(1)(无哈希冲突时)。 |
允许null键/值 | 最多存储一个null键,可存储任意数量的null值。 |
线程不安全 | 非同步实现,多线程并发修改需额外同步(如Collections.synchronizedMap())。 |
内存开销 | 比HashMap大(需存储双向链表的before和after指针)。 |
3.3 常用方法与示例
核心方法(继承自HashMap,新增顺序相关特性):
方法定义 | 功能说明 |
---|---|
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) | 构造器:指定初始容量、加载因子和顺序类型(accessOrder控制插入/访问顺序)。 |
void clear() | 清空所有键值对,重写自HashMap,同时清空双向链表。 |
Set<Map.Entry<K,V>> entrySet() | 返回键值对集合,遍历顺序为链表维护的顺序(插入或访问顺序)。 |
示例1:插入顺序维护
import java.util.LinkedHashMap;
import java.util.Map;public class LinkedHashMapInsertOrderDemo {public static void main(String[] args) {// 默认accessOrder = false:按插入顺序排序Map<String, Integer> linkedMap = new LinkedHashMap<>();linkedMap.put("Apple", 1);linkedMap.put("Banana", 2);linkedMap.put("Cherry", 3);// 遍历:按插入顺序输出System.out.println("插入顺序遍历:");for (Map.Entry<String, Integer> entry : linkedMap.entrySet()) {System.out.println(entry.getKey() + " : " + entry.getValue());}// 输出:Apple : 1 → Banana : 2 → Cherry : 3(与插入顺序一致)}
}
示例2:访问顺序维护(LRU缓存基础)
import java.util.LinkedHashMap;
import java.util.Map;// 按访问顺序排序(最近访问的元素在尾部)
public class LinkedHashMapAccessOrderDemo {public static void main(String[] args) {// accessOrder = true:按访问顺序排序Map<String, Integer> lruMap = new LinkedHashMap<>(16, 0.75f, true);lruMap.put("A", 1);lruMap.put("B", 2);lruMap.put("C", 3);lruMap.get("A"); // 访问"A",触发顺序调整lruMap.get("B"); // 访问"B",触发顺序调整// 遍历:最近访问的元素在尾部System.out.println("访问顺序遍历:");for (Map.Entry<String, Integer> entry : lruMap.entrySet()) {System.out.println(entry.getKey()); // 输出:C → A → B(B最近访问,在最后)}}
}
3.4 适用场景
- 需保留插入顺序的场景(如日志记录、历史操作轨迹、配置项映射)。
- LRU缓存实现(accessOrder = true,最近访问的元素保留,淘汰最久未访问元素)。
- 频繁遍历且需顺序一致的场景(迭代性能优于HashMap,无需遍历哈希表空桶)。
四、TreeMap与LinkedHashMap对比
维度 | TreeMap | LinkedHashMap |
---|---|---|
底层结构 | 红黑树 | 哈希表+双向链表 |
有序性类型 | 键的排序顺序(自然/定制排序) | 插入顺序或访问顺序 |
键要求 | 必须可比较(Comparable/Comparator) | 无特殊要求(支持任意对象) |
键唯一性依据 | compareTo()/compare()返回0 | hashCode()+equals() |
null键支持 | 不允许(抛NullPointerException) | 允许(最多1个null键) |
核心操作性能 | 增删改查O(log n)(红黑树平衡) | 增删改查O(1)(同HashMap,链表维护开销略高) |
迭代性能 | 中(红黑树中序遍历) | 高(链表顺序迭代,无空桶) |
典型应用 | 排序映射、区间查询、导航操作 | 顺序保留、LRU缓存、日志记录 |
五、使用注意事项与最佳实践
5.1 TreeMap注意事项
- 键的比较一致性:排序规则(compareTo()/compare())需与equals()逻辑一致(若compareTo()返回0,则equals()应返回true),否则可能出现“逻辑重复键”。
- 避免频繁修改排序规则:键的比较属性若被修改,可能导致红黑树结构混乱(建议使用不可变对象作为键)。
- 导航方法利用:充分使用ceilingKey()、floorKey()等导航方法,简化区间查询逻辑。
5.2 LinkedHashMap注意事项
- 初始容量设置:同HashMap,预估容量减少扩容次数(如new LinkedHashMap<>(100))。
- LRU缓存实现:重写removeEldestEntry(Map.Entry<K,V> eldest)方法,定义淘汰规则(如超过容量时删除最久未访问元素):
// LRU缓存示例(容量为3) Map<String, Integer> lruCache = new LinkedHashMap<>(3, 0.75f, true) {@Overrideprotected boolean removeEldestEntry(Map.Entry<String, Integer> eldest) {return size() > 3; // 容量超过3时,删除最久未访问元素} };
- 线程安全:多线程环境需通过Collections.synchronizedMap()包装,或使用ConcurrentHashMap(无顺序需求时)。
六、总结
TreeMap和LinkedHashMap作为Map接口的重要实现,分别聚焦“排序能力”和“顺序保留能力”:
- TreeMap:通过红黑树实现键的排序,适用于需按键排序、区间查询或导航操作的场景,核心优势是有序性和导航方法。
- LinkedHashMap:通过哈希表+双向链表保留顺序,适用于需插入/访问顺序一致或LRU缓存的场景,核心优势是顺序可控和高效迭代。
选择时需根据“是否需要排序”