Java 集合:TreeMap 类

TreeMap 是 Java 集合框架中基于红黑树实现的有序 Map 集合,它实现了 NavigableMap 接口(继承自 SortedMap),能够按照键的自然顺序或自定义比较器对键值对进行排序。与 HashMap 相比,TreeMap 牺牲了部分插入和查询性能,换取了有序性和丰富的导航能力。

 

TreeMap 重要特性

  1. 有序性:

    • 默认按照键的自然顺序(实现 Comparable 接口的 compareTo 方法)排序。

    • 可通过构造方法指定自定义比较器(Comparator),灵活定义排序规则。

    • 所有键必须具有可比性(要么实现 Comparable,要么提供 Comparator),否则会抛出 ClassCastException。

  1. 存储结构:

    • 底层采用红黑树(一种自平衡二叉查找树)实现,确保键的有序性和操作效率。

    • 红黑树的特性保证了插入、删除、查询操作的时间复杂度为 O (log n)。

  1. 键值约束:

    • 键不能为 null(会抛出 NullPointerException)。

    • 值可以为 null,且支持多个键对应 null 值。

  1. 非线程安全:

    • 本身不是线程安全的,多线程环境下需手动同步(如使用 Collections.synchronizedSortedMap())。

  1. 导航能力:

    • 提供丰富的导航方法(如获取最小 / 大键、范围查询等),这是其区别于其他 Map 的核心优势。

 

TreeMap 构造方法

TreeMap 提供了 4 种构造方法:

  • TreeMap():创建一个空 TreeMap,使用键的自然顺序排序(要求键实现 Comparable 接口)。

  • TreeMap(Comparator<? super K> comparator):创建一个空 TreeMap,使用指定的比较器排序。

  • TreeMap(Map<? extends K, ? extends V> m):从指定 Map 复制键值对,使用键的自然顺序排序。

  • TreeMap(SortedMap<K, ? extends V> m):从指定 SortedMap 复制键值对,沿用其排序规则。 

  

TreeMap 常用方法

TreeMap 是基于红黑树实现的有序映射,其核心特性是键的自然排序(或自定义比较器排序),所有方法均围绕有序性和红黑树的高效操作展开。以下是其主要方法的详细介绍:

通用方法(继承自 Map)

V put(K key, V value)
  • 功能:向映射中添加键值对,若键已存在则覆盖旧值。

  • 有序性核心:

    • 插入时会根据键的排序规则(默认 Comparable 接口,或构造时指定的 Comparator)将键值对插入红黑树的对应位置,保证树始终有序。

    • 若键为 null:

      • 若使用默认排序(键实现 Comparable),插入 null 会抛出 NullPointerException(因 null 无法调用 compareTo 方法)。

      • 若自定义比较器且支持 null 键,则可正常插入(需在比较器中处理 null 逻辑)。

  • 返回值:返回键之前关联的值(若键不存在则返回 null)。

V get(Object key)
  • 功能:根据键获取对应的值,利用红黑树的二分查找特性,时间复杂度为 O(log n)。

  • 查找逻辑:

    • 通过排序规则(compareTo 或 Comparator)在红黑树中定位键的位置。

    • 若找到匹配的键,返回关联的值;若键不存在或不支持 null 键时传入 null,返回 null。

  • 注意:查找时的 “键匹配” 依赖排序规则,而非 equals 方法(但建议 compareTo 结果与 equals 逻辑一致,避免逻辑矛盾)。

V remove(Object key)
  • 功能:删除指定键对应的键值对,删除后会自动调整红黑树结构,维持有序性和平衡性。

  • 返回值:返回被删除键关联的值(若键不存在,返回 null)。

  • 特殊情况:若键为 null 且不支持 null 键,直接返回 null(不抛出异常)。

boolean containsKey(Object key)
  • 功能:判断映射中是否包含指定键,内部逻辑与 get 方法一致(通过排序规则查找键的位置)。

  • 返回值:找到键则返回 true,否则返回 false(包括键为 null 且不支持的情况)。

int size()
  • 功能:返回映射中键值对的总数量,内部维护一个计数器,插入 / 删除时实时更新,时间复杂度为 O(1)。

  • 注意:返回值为当前有效键值对数量,红黑树的结构调整不影响此计数。

void clear()
  • 功能:清空映射中所有键值对,同时重置红黑树(将根节点置为 null),计数器归零。

  • 行为:清空后 size() 返回 0,isEmpty() 返回 true,后续插入会重新构建红黑树。

K firstKey()
  • 功能:返回映射中最小的键(按排序规则定义的 “最小”)。

  • 实现逻辑:红黑树的最小键对应树的最左子节点,通过循环遍历左子节点直至 null 找到。

  • 异常:若映射为空,调用此方法会抛出 NoSuchElementException。

K lastKey()
  • 功能:返回映射中最大的键(按排序规则定义的 “最大”)。

  • 实现逻辑:红黑树的最大键对应树的最右子节点,通过循环遍历右子节点直至 null 找到。

  • 异常:若映射为空,调用此方法会抛出 NoSuchElementException。

范围查询方法(子 Map 相关)

这类方法用于获取键在指定范围内的子映射,返回的 SortedMap 是原映射的视图(修改子映射会同步影响原映射,反之亦然)。

SortedMap<K, V> headMap(K toKey)
  • 功能:返回键严格小于 toKey 的所有键值对构成的子映射。

  • 注意:

    • 子映射的键范围是 [最小键, toKey)(左闭右开)。

    • 若 toKey 小于原映射的最小键,返回空的子映射;若 toKey 大于等于最大键,返回原映射的完整视图。

SortedMap<K, V> tailMap(K fromKey)
  • 功能:返回键大于等于 fromKey 的所有键值对构成的子映射。

  • 注意:子映射的键范围是 [fromKey, 最大键](左闭右闭)。

SortedMap<K, V> subMap(K fromKey, K toKey)
  • 功能:返回键在 [fromKey, toKey) 范围内(左闭右开)的所有键值对构成的子映射。

  • 异常:若 fromKey 大于 toKey,或 fromKey/toKey 超出原映射的键范围(且比较器不支持),会抛出 IllegalArgumentException。

邻近键值对查询方法

这类方法用于查找与指定键 “最接近” 的键值对(Entry),返回结果包含键和值,适用于需要 “近似匹配” 的场景。

Map.Entry<K, V> lowerEntry(K key)
  • 功能:返回键严格小于 key 的所有键中,最大键对应的键值对(即 “小于 key 的最大 Entry”)。

  • 返回值:找到则返回该 Entry,若不存在(如 key 小于所有键)则返回 null。

Map.Entry<K, V> higherEntry(K key)
  • 功能:返回键严格大于 key 的所有键中,最小键对应的键值对(即 “大于 key 的最小 Entry”)。

  • 返回值:找到则返回该 Entry,若不存在(如 key 大于所有键)则返回 null。

Map.Entry<K, V> floorEntry(K key)
  • 功能:返回键小于或等于 key 的所有键中,最大键对应的键值对(即 “不大于 key 的最大 Entry”)。

  • 区别于 lowerEntry:若 key 本身存在于映射中,直接返回 key 对应的 Entry;lowerEntry 则返回小于 key 的最大 Entry。

Map.Entry<K, V> ceilingEntry(K key)
  • 功能:返回键大于或等于 key 的所有键中,最小键对应的键值对(即 “不小于 key 的最小 Entry”)。

  • 区别于 higherEntry:若 key 本身存在于映射中,直接返回 key 对应的 Entry;higherEntry 则返回大于 key 的最小 Entry。

首尾键值对删除方法

Map.Entry<K, V> pollFirstEntry()
  • 功能:删除并返回映射中最小键对应的键值对(等同于先调用 firstKey() 找到最小键,再调用 remove() 删除)。

  • 返回值:删除的 Entry,若映射为空则返回 null(不抛出异常,区别于 firstKey())。

Map.Entry<K, V> pollLastEntry()
  • 功能:删除并返回映射中最大键对应的键值对(等同于先调用 lastKey() 找到最大键,再调用 remove() 删除)。

  • 返回值:删除的 Entry,若映射为空则返回 null。

其他扩展方法

NavigableSet<K> descendingKeySet()
  • 功能:返回键的逆序集合视图(即按原排序规则的相反顺序排列),返回的 NavigableSet 支持反向迭代、反向范围查询等操作。

  • 行为:该集合是原映射的视图,修改原映射会同步更新此集合;迭代时按 “从大到小” 的顺序遍历键。

  • 示例:若原映射键按 1,2,3 排序,descendingKeySet() 遍历结果为 3,2,1。

  

TreeMap 简单示例

自然顺序排序(键实现 Comparable)

下面将演示,当 TreeMap 未指定自定义比较器(Comparator)时,TreeMap 会依赖键(key)自身实现的 Comparable 接口来确定排序规则。是 TreeMap 最常用的排序模式之一。例如:

package com.hxstrive.java_collection.treeMap;

import java.util.TreeMap;
import java.util.Map;

public class TreeMapExample {
    public static void main(String[] args) {
        // 键为 Integer(已实现 Comparable),默认按自然顺序(升序)排序
        TreeMap<Integer, String> numMap = new TreeMap<>();
        
        numMap.put(3, "Three");
        numMap.put(1, "One");
        numMap.put(2, "Two");
        numMap.put(5, "Five");
        numMap.put(4, "Four");
        
        // 迭代顺序为键的升序
        System.out.println("自然顺序(升序):");
        for (Map.Entry<Integer, String> entry : numMap.entrySet()) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
        
        // 导航方法示例
        System.out.println("\n最小键:" + numMap.firstKey());          // 1
        System.out.println("最大键:" + numMap.lastKey());           // 5
        System.out.println("小于3的最大键值对:" + numMap.lowerEntry(3));  // 2=Two
    }
}

运行结果:

自然顺序(升序):
1=One
2=Two
3=Three
4=Four
5=Five

最小键:1
最大键:5
小于3的最大键值对:2=Two

Process finished with exit code 0

自定义比较器(降序排序)

TreeMap 还支持通过自定义 Comparator 实现灵活的排序规则,包括降序排序。当 TreeMap 构造时传入自定义比较器,会优先使用该比较器替代键的自然排序(Comparable 接口),从而实现键的降序排列。例如:

package com.hxstrive.java_collection.treeMap;

import java.util.TreeMap;
import java.util.Comparator;

public class TreeMapComparatorExample {
    public static void main(String[] args) {
        // 自定义比较器:按字符串长度降序排序
        TreeMap<String, Integer> strMap = new TreeMap<>(
            Comparator.comparingInt(String::length).reversed()
        );
        
        strMap.put("apple", 5);
        strMap.put("banana", 6);
        strMap.put("pear", 4);
        strMap.put("orange", 6);  // 长度与banana相同,按自然顺序排列
        
        System.out.println("按字符串长度降序:");
        System.out.println(strMap);
        strMap.forEach((k, v) -> System.out.println(k + "(长度:" + v + ")"));
    }
}

运行结果:

按字符串长度降序:
{banana=6, apple=5, pear=4}
banana(长度:6)
apple(长度:5)
pear(长度:4)

观察输出结果,你会发现“orange”没有被放入到 Map 中,这是为什么呢?

TreeMap 判定两个键是否 “相等” 的依据是比较器的返回值:若 compare(k1, k2) == 0,则认为 k1 和 k2 是同一个键,新键值对会覆盖旧键值对(键不会重复添加)。

当执行 strMap.put("orange", 6) 时:

  • TreeMap 发现 orange 与已存在的 banana 在比较器中返回 0 → 判定为同一键。

  • 此时会用新值(6)覆盖旧值(6,值相同不影响),但键仍然是 banana(因为先放入的键会被保留,后放入的同键会覆盖值但不改变键)。

因此,最终 TreeMap 中只会保留 banana 这个键,orange 作为 “重复键” 被忽略(仅覆盖值,键未新增)。简单修改一下代码进行验证:

package com.hxstrive.java_collection.treeMap;

import java.util.TreeMap;
import java.util.Comparator;

public class TreeMapComparatorExample {
    public static void main(String[] args) {
        // 自定义比较器:按字符串长度降序排序
        TreeMap<String, Integer> strMap = new TreeMap<>(
            Comparator.comparingInt(String::length).reversed()
        );

        strMap.put("apple", 5);
        strMap.put("banana", 6);
        strMap.put("pear", 4);
        strMap.put("orange", 16);  // 长度与banana相同,按自然顺序排列

        System.out.println("按字符串长度降序:");
        System.out.println(strMap);
        strMap.forEach((k, v) -> System.out.println(k + "(长度:" + v + ")"));
    }
}

运行结果:

按字符串长度降序:
{banana=16, apple=5, pear=4}
banana(长度:16)
apple(长度:5)
pear(长度:4)

此时输出结果中,banana 只为 16 了,值被覆盖了。

  

与其他 Map 的对比

TreeMap 是 Java 集合框架中具有有序性的 Map 实现,与其他常见 Map(如 HashMap、LinkedHashMap、Hashtable、ConcurrentHashMap 等)相比,在数据结构、排序特性、性能等方面有显著差异。如下表:

特性

TreeMap

HashMap

LinkedHashMap

有序性

自然顺序或自定义顺序

无序

插入顺序或访问顺序

底层结构

红黑树

哈希表

哈希表 + 双向链表

查找效率

O(log n)

平均 O (1)

平均 O (1)

插入 / 删除效率

O(log n)

平均 O (1)

平均 O (1)(额外链表操作)

键是否允许为 null

不允许

允许(1 个)

允许(1 个)

导航能力

丰富(范围查询等)

  

TreeMap 适用场景

下面是 TreeMap 适用场景:

(1)需要有序键值对的场景:如排行榜(按分数排序)、字典(按字母顺序排序)等。

(2)范围查询场景:需频繁执行「获取小于 / 大于某个键的元素」「获取区间内元素」等操作时。

(3)自定义排序规则场景:键的自然顺序不符合需求,需通过比较器灵活定义排序逻辑时。

注意,TreeMap 是有序 Map 的首选实现,尤其适合需要频繁进行范围查询和排序操作的场景,虽然性能略低于 HashMap,但提供了更强大的有序性和导航能力。

更多信息参考 https://docs.oracle.com/javase/8/docs/api/java/util/TreeMap.html API 文档。

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号