TreeMap中的常用方法详解

目录

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 总结 


1 引言

在编程的世界里,选择合适的数据结构对于解决问题至关重要。Java 提供了多种数据结构以满足不同的需求,其中之一便是 TreeMap。作为一种特殊的 Map 实现,TreeMap 不仅能够存储键值对,还能根据键的自然顺序或者通过提供的比较器进行排序。这种特性使得 TreeMap 成为了处理需要有序数据访问场景的理想选择。

本文旨在为基础学习者提供一个清晰易懂的指南,介绍 TreeMap 中最常用的几种方法,以及如何利用它们来简化数据管理任务。我们将从最基本的构造方法开始,逐步讲解如何使用 TreeMap 来完成日常开发中的各种操作,并通过简单的示例帮助你更好地理解和应用这些知识。无论你是刚开始接触 Java 集合框架的新手,还是希望加深对 TreeMap 理解的开发者,本文都将为你打下坚实的基础。让我们一起进入 TreeMap 的世界,探索它为我们带来的便捷与强大功能吧。

2  TreeMap简介

2.1 定义

TreeMap 是 Java 集合框架 (java.util 包) 中的一个类,用于存储键值对 (Key-Value Pair) 的集合。它是基于红黑树(Red-Black Tree)数据结构实现的,能够自动根据键的自然顺序或者通过提供的比较器(Comparator)对键进行排序。由于其底层使用了自平衡二叉搜索树,TreeMap 提供了高效的插入、删除和查找操作,时间复杂度为 O(log n)。

2.2 特点

  • 键值对存储:每个元素由一个键(Key)和一个值(Value)组成,键不允许重复。
  • 有序性:键按照自然顺序(如数字从小到大、字符串按字典序)或通过指定的比较器进行排序。
  • 不允许 null 键:键不能为 null,否则会抛出 NullPointerException,但值可以为 null
  • 非线程安全TreeMap 不是线程安全的,如果需要在多线程环境中使用,可以通过 Collections.synchronizedSortedMap 方法对其进行包装。
  • 底层实现:基于红黑树(一种自平衡二叉搜索树),确保了插入、删除和查找操作的时间复杂度为 O(log n)。

2.3 适用场景

  • 当需要按键的顺序访问数据时(例如成绩排名、日志时间戳排序)。
  • 当需要频繁执行范围查询(如获取某个区间内的键值对)。
  • 当需要快速找到最大或最小的键值对。

TreeMap的核心类和方法

3.1 Entry

TreeMap 中的每个元素被封装在一个 Entry 对象中。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;
    }
}

3.2 核心方法 

3.2.1 put(K key, V value) 方法

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 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 k = (Comparable) 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 方法对红黑树进行结构调整和重新着色,确保其满足红黑树的性质(如根节点为黑色、无相邻红色节点等),从而维持树的平衡性和高效性。

3.2.2 get(Object key) 方法 

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 k = (Comparable) 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
}

流程概述:

1. 查找节点

从根节点开始,根据键的大小逐步向下查找目标节点。比较逻辑通过 Comparator(自定义比较器)或键的自然顺序(实现 Comparable 接口)来完成。每次比较后,决定向左子树还是右子树移动,直到找到匹配的节点或到达叶子节点。

2. 返回结果

  • 如果成功找到目标节点,则返回该节点对应的值。
  • 如果未找到目标节点(即键不存在),则返回 null

 4 其他常用方法

  • 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中的常用方法详解_第1张图片5 总结 

TreeMap 是 Java 集合框架中一个功能强大且高效的工具,适用于需要有序存储和访问键值对的场景。通过本文的介绍,我们从 TreeMap 的定义、特点、底层实现到核心方法的详细解析,逐步揭开了它的神秘面纱。希望本文能对你有所帮助!

你可能感兴趣的:(数据结构,开发语言,java,数据结构)