TreeMap 与 TreeSet

        在日常开发中,我们经常会遇到动态查找 + 键有序的场景 —— 比如按用户等级排序展示信息、统计商品销量后按销量降序排列、管理有序的配置项等。此时,HashMap/HashSet 虽然能提供 O (1) 的高效操作,但 “无序” 的特性会让后续排序成本陡增;而普通二叉搜索树又可能因插入顺序问题退化成单支树,导致性能骤降。

        TreeMap 与 TreeSet 正是为解决这类问题而生 —— 它们底层基于红黑树(一种近似平衡的二叉搜索树),既保证了 Key 的有序性,又将插入、删除、查找的时间复杂度稳定在 O (logN) 级别。今天我们就来拆解这两个集合类的核心逻辑、使用技巧与实战场景。

一、先搞懂底层:红黑树

        在聊 TreeMap 和 TreeSet 之前,必须先提它们的 “基石”—— 红黑树。因为 TreeMap/TreeSet 的所有特性(有序、高效)都源于红黑树的设计:

        红黑树是一种自平衡的二叉搜索树,在普通二叉搜索树的基础上增加了 “颜色规则”(每个节点非红即黑),通过旋转和颜色调整,确保树的高度始终维持在 O (logN) 级别(近似平衡)。这就解决了普通二叉搜索树可能退化成 “单支树”(时间复杂度 O (N))的痛点。

        简单来说,红黑树的核心作用是:保证 Key 的有序性(二叉搜索树特性)+ 稳定的高效操作(平衡特性) —— 这也是 TreeMap/TreeSet 的核心优势。

二、TreeMap:有序键值对的 “管理器”

        TreeMap 是 Map 接口的实现类,专门存储<K, V>键值对,核心特点是Key 有序且唯一

1. TreeMap 的核心特性

基于红黑树的底层设计,TreeMap 有以下关键特性,这些也是开发中必须注意的点:

  • Key 唯一,Value 可重复:红黑树的特性确保 Key 不会重复(插入重复 Key 会覆盖 Value),但 Value 没有唯一性限制。
  • Key 不能为空,Value 可以为空:若插入 Key 为null,会直接抛出NullPointerException;但 Value 允许为null
  • Key 有序:默认按 Key 的 “自然顺序”(如 Integer 升序、String 字典序)排列,也可通过自定义Comparator指定排序规则。
  • 线程不安全:与 HashMap 类似,TreeMap 没有同步机制,多线程环境下需手动加锁(如使用Collections.synchronizedSortedMap)。
  • 查找效率稳定:插入、删除、查找的时间复杂度均为 O (logN),不受数据插入顺序影响(红黑树的平衡特性保障)。

2. TreeMap 的常用方法与实战

        TreeMap 提供了 Map 接口的所有核心方法,这里挑几个高频使用的重点讲解,并结合实战案例说明:

方法核心作用注意点
V put(K key, V value)插入键值对:Key 不存在则插入,返回null;Key 存在则覆盖 Value,返回旧 Value触发红黑树平衡调整
V get(Object key)根据 Key 获取 Value,Key 不存在返回null按红黑树规则查找,效率 O (logN)
V getOrDefault(Object key, V defaultValue)同上,但 Key 不存在时返回默认值避免手动判空,简化代码
boolean containsKey(Object key)判断是否包含指定 Key效率 O (logN)(红黑树查找)
boolean containsValue(Object value)判断是否包含指定 Value效率 O (N)(需遍历所有 Value)
Set<K> keySet()返回所有 Key 的有序 Set 集合迭代顺序与 Key 的排序规则一致
Set<Map.Entry<K, V>> entrySet()返回所有键值对的 Set 集合遍历键值对的最优方式(避免二次查找)
实战案例:用 TreeMap 管理 “学生成绩排行榜”

        需求:存储学生姓名(Key)与分数(Value),按分数降序排列,若分数相同则按姓名升序排列。

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

public class TreeMapDemo {
    public static void main(String[] args) {
        //1. 初始化TreeMap,指定自定义比较器(分数降序,姓名升序)
        Map<String, Integer> scoreMap = new TreeMap<>(new Comparator<String>() {
            @Override
            public int compare(String name1, String name2) {
                //先获取两个学生的分数
                int score1 = scoreMap.getOrDefault(name1, 0);
                int score2 = scoreMap.getOrDefault(name2, 0);
                //分数降序:分数高的在前
                if (score2 != score1) {
                    return score2 - score1;
                }
                //分数相同则按姓名升序(字典序)
                return name1.compareTo(name2);
            }
        });

        //2. 插入数据
        scoreMap.put("张三", 95);
        scoreMap.put("李四", 88);
        scoreMap.put("王五", 95); //与张三分数相同,按姓名排序
        scoreMap.put("赵六", 92);

        //3. 遍历键值对(有序输出)
        System.out.println("学生成绩排行榜:");
        for (Map.Entry<String, Integer> entry : scoreMap.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue() + "分");
        }

        //4. 常用操作示例
        System.out.println("\n张三的分数:" + scoreMap.get("张三")); //95
        System.out.println("是否包含孙七?" + scoreMap.containsKey("孙七")); //false
        System.out.println("孙七的分数(默认0):" + scoreMap.getOrDefault("孙七", 0)); //0
    }
}

输出结果会按 “分数降序 + 姓名升序” 排列,符合我们的需求:

三、TreeSet:有序去重的 “筛选器”

        TreeSet 是 Set 接口的实现类,核心作用是存储唯一、有序的 Key,可以理解为 “只存 Key 的 TreeMap”。

1. TreeSet 的核心特性

        TreeSet 的底层是基于 TreeMap 实现的(将 TreeSet 的 Key 作为 TreeMap 的 Key,Value 用一个固定的空对象PRESENT填充),因此它的特性与 TreeMap 高度一致:

  • 只存 Key,且 Key 唯一:天然支持去重(插入重复 Key 会返回false,不生效)。
  • Key 不能为空:插入null会抛出NullPointerException
  • Key 有序:默认按自然顺序,也可通过Comparator自定义排序。
  • 线程不安全:无同步机制,多线程需手动处理。
  • 操作效率 O (logN):依赖 TreeMap 的红黑树实现,插入、删除、查找均为 O (logN)。

2. TreeSet 的常用方法与实战

TreeSet 继承自 Collection,常用方法聚焦于 “添加、判断、删除” 等 Set 核心操作:

方法核心作用注意点
boolean add(E e)添加元素:元素不存在则插入,返回true;存在则返回false去重的核心方法
boolean contains(Object o)判断元素是否存在,存在返回true效率 O (logN)
boolean remove(Object o)删除元素:存在则删除并返回true;不存在返回false需注意红黑树的平衡调整
Iterator<E> iterator()返回有序迭代器迭代顺序与 Key 的排序规则一致
int size()返回元素个数直接获取,O (1) 效率
void clear()清空所有元素清空红黑树,O (N) 效率
实战案例:用 TreeSet 实现 “有序去重的商品分类”

        需求:存储商品分类名称,要求去重且按 “分类名称长度升序” 排列(长度相同则按字典序)。

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        //1. 初始化TreeSet,自定义排序规则:按名称长度升序,长度相同按字典序
        TreeSet<String> categorySet = new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String c1, String c2) {
                //先按长度升序
                if (c1.length() != c2.length()) {
                    return c1.length() - c2.length();
                }
                //长度相同按字典序
                return c1.compareTo(c2);
            }
        });

        //2. 添加元素(测试去重:重复的“电子产品”不会被插入)
        categorySet.add("电子产品");
        categorySet.add("服装");
        categorySet.add("食品");
        categorySet.add("电子产品"); //重复元素,添加失败
        categorySet.add("家居用品");

        //3. 遍历(有序输出)
        System.out.println("有序去重的商品分类:");
        Iterator<String> iterator = categorySet.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        //4. 常用操作示例
        System.out.println("\n是否包含“化妆品”?" + categorySet.contains("化妆品")); //false
        System.out.println("删除“食品”:" + categorySet.remove("食品")); //true
        System.out.println("删除后分类:" + categorySet);
    }
}

输出结果会按 “长度升序 + 字典序” 去重排列:

四、TreeMap 与 TreeSet 的关联与区别

1. 核心关联:TreeSet 依赖 TreeMap 实现

        TreeSet 的底层源码非常简单 —— 它内部持有一个 TreeMap 实例,将所有添加的元素作为 TreeMap 的 Key,Value 则用一个静态常量PRESENTprivate static final Object PRESENT = new Object())填充。

也就是说:

  • TreeSet 的 “去重” 本质是 TreeMap 的 Key 唯一。
  • TreeSet 的 “有序” 本质是 TreeMap 的 Key 有序。
  • TreeSet 的所有操作(add、remove、contains)最终都委托给 TreeMap 的对应方法。

2. 核心区别:存储模型不同

维度TreeMapTreeSet
存储模型<K, V>键值对仅 Key(纯 Key 模型)
核心用途需关联 Key 与 Value 的场景(如排行榜、配置表)仅需去重 + 有序的场景(如分类、标签)
特有方法get(K key)entrySet()等 Map 专属方法无特有方法,仅 Set 接口方法

五、TreeMap/TreeSet vs HashMap/HashSet:该怎么选?

        很多时候我们会纠结:到底用 Tree 系列还是 Hash 系列?其实核心看 “是否需要有序”—— 这是两者的核心分水岭。

对比维度TreeMap/TreeSetHashMap/HashSet
底层结构红黑树哈希桶(数组 + 链表 / 红黑树)
时间复杂度插入 / 删除 / 查找均为 O (logN)平均 O (1),最坏 O (logN)(链表转红黑树后)
Key 有序性有序(自然顺序 / 自定义比较器)无序(JDK8 后为插入顺序,非自然顺序)
Key 是否允许 null不允许(抛 NPE)允许(HashMap/HashSet 均可存 1 个 null Key)
比较逻辑依赖ComparableComparator依赖hashCode()equals()
核心场景需有序的动态查找(如排行榜、排序数据)无需有序,追求极致性能(如缓存、高频查询)

六、使用 TreeMap/TreeSet 的注意事项

  1. 自定义 Key 必须实现比较逻辑若用自定义类(如UserBook)作为 Key,必须要么让类实现Comparable接口(重写compareTo方法),要么在初始化 TreeMap/TreeSet 时传入Comparator—— 否则插入元素时会抛出ClassCastException(无法比较 Key)。

  2. Key 不可直接修改TreeMap/TreeSet 的 Key 有序性依赖 Key 本身的值,若直接修改 Key 的属性(如将Userid从 1 改为 10),会破坏红黑树的结构,导致后续查找、删除异常。正确做法是:先删除旧 Key,再插入新 Key。

  3. containsValue效率低TreeMap 的containsValue方法需要遍历所有 Value(O (N) 效率),若需频繁判断 Value 是否存在,建议反向维护一个TreeMap<V, K>(空间换时间)。

  4. 线程不安全需手动同步多线程环境下,若需并发操作 TreeMap/TreeSet,需使用Collections.synchronizedSortedMapCollections.synchronizedSortedSet包装,或使用ConcurrentSkipListMap(JUC 提供的并发有序 Map)。

总结

        TreeMap 与 TreeSet 是 Java 集合框架中 “有序集合” 的代表,它们基于红黑树解决了 “有序 + 高效” 的核心需求:

  • TreeMap:用<K, V>键值对管理有序数据,适合需要关联 Key 与 Value 的场景(如排行榜、配置表)。
  • TreeSet:用纯 Key 模型实现去重 + 有序,适合仅需筛选有序唯一数据的场景(如分类、标签)。

        选择时记住一个核心原则:需要有序用 Tree 系列,追求性能用 Hash 系列。掌握它们的底层逻辑和使用场景,能让我们在处理动态查找问题时更加得心应手。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值