TreeMap学习(基于JDK 1.8)

  • JDK 1.8开始,HashMap中冲突的entry数大于8,会将链表转为红黑树,以减少查询耗时
  • 在学习红黑树的过程中,了解到TReeMap使用红黑树存储entry
  • 为了加深对红黑树的理解,基于JDK 1.8源码,学习TreeMap
  • 说实话,本人好像从未使用过TreeMap 😂

1. TreeMap与HashMap的异同

1.1 相同点

  1. key不能重复
    (1)作为map,要求不能包含重复的key,每个key至多映射到一个value;
    (2)写入相同的key,旧的value将被覆盖
  2. 非线程安全:可以通过工具方法Collections.synchronizedMap(Map<K,V> m)转化为线程安全的map
  3. 均使用fail-fast迭代器:
    (1)一旦创建好迭代器,除非使用迭代器的remove()方法改变map结构,都将使迭代器抛出ConcurrentModificationException异常
    (2)在检测到map结构发生改变后,立即抛出异常,遍历失败并停止。这就是所谓的fail-fast机制
    (3)多线程修改map结构时,很容易触发fail-fast机制(单线程也可以触发)

fail-fast机制的验证(机制讲解

  • 下面是fail-fast机制的验证代码:遍历时,通过put操作修改map,会触发ConcurrentModificationException异常
    HashMap<Integer, String> map = new HashMap<>();
    map.put(1, "lucy");
    map.put(2, "jack");
    map.put(3, "grace");
    
    for (Integer key : map.keySet()) {
        // 遍历时,可以执行remove操作
        System.out.println(key + ", " + map.get(key));
        if (key == 3) {
            map.remove(key);
        }
    }
    
    for (Integer key : map.keySet()) {
        // 遍历时,执行remove之外的操作,抛出ConcurrentModificationException
        if (key == 1) {
            map.put(4, "200");
            System.out.println("使用put方法添加entry");
        }
    }
    
  • 执行结果如下
    在这里插入图片描述

1.2 不同点

  1. 底层实现不同:
    (1)HashMap的实现基于哈希表,使用桶(bucket) + 链表 + 红黑树(JDK 1.8之后);
    (2)TreeMap的实现基于红黑树,其entry就是红黑树的节点

  2. 有序性:
    (1)HashMap基于哈希表实现,可以快速查找元素,但失去了元素的插入顺序;
    (2)TreeMap基于红黑树实现,根据key的自然顺序或自定义的Comparator进行排序,元素遍历结果是有序的

  3. null值
    (1)HashMap允许最多一个key为null,允许多个value为null;
    (2)TreeMap不允许key为null,允许多个value为null;

    自己的猜想:

    • HashMap中,多个key为null,则哈希冲突后,无法通过比较key的值查找到对应的entry;因此,只允许最多一个key为null
    • TreeMap中,entry的插入位置取决于key的比较,key为null无法进行比较
    • HashMap和TreeMap都是通过key决定插入位置,value是否为null不影响构建
  4. 性能
    (1)HashMap基于哈希表实现,一般情况下,插入、删除、查找都是 O ( 1 ) O(1) O(1)的时间复杂度;遇到冲突严重时,基于链表的实现将会退化成 O ( n ) O(n) O(n)的时间复杂度;因此JDK 1.8以后,采用链表 + 红黑树
    (2)TreeMap基于红黑树实现,无论最好还是最坏的情况,插入、删除、查找都是 O ( l o g 2 N ) O(log_2N) O(log2N)的时间复杂度

1.3 使用选择

  • 想要遍历的结果有序,使用TreeMap;否则,使用性能更好的HashMap
  • PS:到目前为止,自己无论leetcode刷题,还是实际编程,几乎都使用HashMap

参考文档:


2. TreeMap的定义

2.1 TreeMap的类图

  • 如果使用IntelliJ IDEA(貌似要求旗舰版),可以查看一个类的UML类图
  • TreeMap的类图如下:
    在这里插入图片描述

从类图解读TreeMap

  1. 直观地看:

    (1)TreeMap继承了抽象类AbstractMap,实现了NavigableMapCloneableSerializable三大接口
    (2)由此可见,TreeMap是一个key-value结合,并且支持clone、序列化、元素查找时的导航

  2. 类图的顶层是Map接口:

    (1)用于将key映射到值的对象,key不允许重复,且每个key至多能映射一个值
    (2)Map以接口的形式替代Dictionary这个完全抽象的类
    (3)接口替代抽象类的原因猜测:Java是单继承,将map定义为抽象类,极大地限制了子类的拓展
    (4)总结: Map接口是map体系的基础接口,定义了各种与map有关的操作(方法)

  3. AbstractMap抽象类:

    (1)实现Map接口,提供了对Map接口的骨架实现(最简单的实现),以减少实现Map接口所需的工作量
    (2)说是骨架实现,一点都不过分,put() 方法直接抛出UnsupportedOperationException异常

  4. SortedMap 接口

    (1)继承Map接口,在Map接口的基础上,提供一个有序的map接口
    (2)按照key的自然顺序或创建时指定的比较器Comparator,对所有的key进行排序(提供key的全序)
    (3)SortedMap提供了一些可以充分利用排序的方法,如 firstKey()subMap()

  5. NavigableMap 接口:
    (1)继承SortedMap接口,增加了返回给定搜索目标最近匹配项的导航方法
    (2)例如,higherEntry(K key) 方法,返回比给定key更大的、最小的键对应的键值对;没有匹配的键,则返回null

  6. Cloneable 接口

    (1)在学习Java的浅拷贝与深拷贝时,我们提到过:要想能使用从Object类继承来的 clone()方法,必须要重写 clone() 方法;而重写 clone() 方法,要求实现Cloneable接口
    (2)TreeMap实现了Cloneable接口,说明TreeMap支持clone操作

  7. Serializable 接口:意味着TreeMap支持序列与反序列化

总结:

  • TreeMap继承一个抽象类,实现了三个接口
  • AbstractMap抽象类,使其具有普通的map类所具有的的特性
  • NavigableMap接口,不仅使得TeeMap中的元素有序,还支持返回给定目标最近匹配项的导航方法
  • Cloneable 接口,使得TreeMap支持clone操作
  • Serializable 接口,意味着TreeMap支持序列化和反序列化

参考文档: JAVA学习-TreeMap详解

2.2 成员属性

  • TreeMap的成员属性定义如下
    (1)transient关键字的简单介绍:JAVA中transient关键字的使用

    // 维护TreeMap中entry的顺序,比较器为null,表示使用key的自然顺序进行排序
    private final Comparator<? super K> comparator;
    
    // 红黑树的根节点,使用transient修饰,表示该字段不参与序列化和反序列化
    private transient Entry<K,V> root;
    
    // 红黑树中的节点数
    private transient int size = 0;
    
    // 红黑树结构被修改的次数(与迭代器的fail-fast机制有关)
    private transient int modCount = 0;
    
  • 其中,Entry的定义如下

    // 颜色变量的定义
    private static final boolean RED   = false;
    private static final boolean BLACK = true;
    static final class Entry<K,V> implements Map.Entry<K,V> {
         K key; // key
         V value; // value 
         Entry<K,V> left; // 左子节点
         Entry<K,V> right; // 右子节点
         Entry<K,V> parent; // 父节点
         boolean color = BLACK; // 初始时,节点颜色为黑色
    }
    
  • Entry的构造函数如下:
    (1)只有一个构造函数,入参为key、value和parent;left、right、color都将使用默认值或定义时的初始值
    (2)因此,新的红黑树节点,其左右子节点将为null,颜色为黑色

    Entry(K key, V value, Entry<K,V> parent) {
        this.key = key;
        this.value = value;
        this.parent = parent;
    }
    

红黑树节点的默认颜色为红色?

  • 至此,可能会出现这样的疑问:Entry中color字段初始值为黑色,那就是说红黑树节点的默认颜色应该为黑色?
  • 为什么还有博客说红黑树节点颜色默认为红色?
  • put()方法,新增红黑树节点,初始时确实是黑色;若该节点不是根节点,则会调用fixAfterInsertion()方法进行树的结构调整
  • fixAfterInsertion()方法,首先将color改成了红色。因为,若新增节点为黑色,很容易导致黑色高度不一致
  • 这也是有的博客会说:红黑树节点的默认颜色为红色
  • 其实,这仅限于插入操作,新增节点不是根节点且需要做树的结构调整时的情况

2.3 构造函数

  • TreeMap的构造函数如下:
    (1)一般,前两种构造函数更为常用:创建一个空的、支持自然顺序或指定比较器排序的TreeMap
    (2)创建空的TreeMap,红黑树的也为空;即root为null、size为0、modCount为0(尚未发生过结构修改)
    /**
    * 构建一个空的、使用key的自然顺序排序的TreeMap;
    * 要求:key必须实现了Comparable接口,即支持比较操作
    **/
    public TreeMap()
    
    /**
    * 构建一个空的、使用自定比较器进行排序的TreeMap;
    * 要求:key必须实现了Comparable接口,即支持比较操作
    **/
    public TreeMap(Comparator<? super K> comparator) 
    
    /**
    * 基于已有的map,构建一个使用key的自然顺序排序的TreeMap;
    * 要求:key必须实现了Comparable接口,即支持比较操作
    **/
    public TreeMap(Map<? extends K, ? extends V> m)
    
    /**
    * 基于已有的sortedMap,构建一个TreeMap;
    * 该TreeMap使用sortedMap中的比较器
    **/
    public TreeMap(SortedMap<K, ? extends V> m)
    

3. 重要方法解读

3.1 get方法

  • 回想之前会红黑树的介绍:它是一棵近似平衡的二叉搜索树

  • 因此,通过key查找对应的value时,可以通过比较key的大小缩小查找范围(左子树或右子树)

  • TreeMap的get()方法代码如下
    (1)存在key的映射时,返回key对应的value(可能为null);不存在key的映射,返回null
    (2)抛出NullPointerException 异常的情况:

    • key为 null且使用key的自然顺序,即未指定比较器,由getEntry()方法主动抛出
      • 因为,查找entry依赖给定key与其他key的比较操作
    • 自定义的比较器不支持key为null,由比较器的compare()方法抛出

    (3)抛出ClassCastException异常的情况:给定的key不能与map中的key相比较,即二者类型不兼容的情况

    public V get(Object key) {
    	// 调用getEntry(),根据key查找对应的entry
        Entry<K,V> p = getEntry(key);
        // 如果返回的entry为null,说明没有key对应的entry
        return (p==null ? null : p.value);
    }
    
  • getEntry()方法
    (1)自定义比较器时,将调用getEntryUsingComparator()查找key对应的Entry
    (2)使用key的自然顺序,要求key不能为null且实现了Comparable接口
    (3)Comparable接口只有一个compareTo()方法,属于函数式接口
    (4)只有key实现了Comparable接口,才能实现与其他key的比较

    final Entry<K,V> getEntry(Object key) {
        // 给定了比较器,则基于比较器定义的规则进行比较(使用比较器的compare()方法做比较)
        if (comparator != null)
            return getEntryUsingComparator(key);
        // 为指定比较器,要求key不能为null
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        // 利用二叉搜索树的特性,不断缩小范围、迭代搜索红黑树
        Entry<K,V> 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;
        }
        // 使用key的自然顺序,未找到对应的entry
        return null;
    }
    
  • 基于自定义比较器,查找key对应的Entry:
    (1)调用比较器的compare()方法实现比较

    final Entry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
            K k = (K) key;
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            Entry<K,V> p = root;
            while (p != null) {
                // 使用compare,比较给定key和当前节点的key
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
    }
    

get()方法总结

  1. get是获取映射到指定key的value,并非get整个Entry
  2. get()方法实质:查找指定key对应的Entry,返回Entry中的value或null
  3. getEntry()时,分为两种情况:TreeMap指定了比较器、未指定比较器;未指定比较器时,使用key自带的排序能力(要求实现 Comparable 接口)
  4. getEntry()在执行时,会抛出NullPointerException异常或ClassCastException异常

3.1.1 Comparable和Comparator的区别

  1. Comparable和Comparator都是接口(在这之前,自己一直将 Comparator 理解成了泛型类)

  2. 实现了 Comparable 接口的类,将拥有排序能力,这种排序能力叫做自然排序能力。即天生就具备的排序能力,与后期添加的排序能力相区别

  3. 实现了 Comparable 接口的类,可以直接通过Collections.sort()Arrays.sort()实现排序

  4. 以下情况,可以考虑借助Comparator 接口实现类的自定义排序
    (1)一个类天生不具备自然排序能力,又需要实现对象之间的比较或排序操作;
    (2)一个类的自然排序方式不满足要求(默认升序,现需要降序),需要自定义排序方式

  5. Comparator接口中有很多方法,但大都都是静态方法default方法,只需要实现compare()方法就可以让类拥有定制化的排序能力

  6. 注意: 并非是类去实现Comparator接口,而是将类作为泛型参数,为该类创建一个实现了Comparator接口的比较器类

    // 一般使用匿名类方式实现Comparator接口
    Comparator<Integer> descComparator = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    };
    // 甚至使用lambda表达式,可以简写如下
    Comparator<Integer> descComparator = (o1, o2) -> o2 - o1;
    

总结

  • 实现Comparable接口的类,可以实现自然排序;在创建类时,就需要做好规划,否则需要对类进行二次改动
  • 实现Comparator接口、创建基于现有类的比较器类,可以让现有类拥有自定义的排序能力,却又无需修改现有类

参考文档:

3.2 containsKey方法

  • get()方法的方法注释中,有这样几句话:
    • 如果返回的值为null,可能是map中不存在给定key的映射,也可能是value本身为null。
    • 判断map是否存在给定key的映射,应该使用containsKey(),而非get()方法
  • 综上,containsKey()方法用于判断map中是否存在给定key的映射
  • 假设让我们借鉴get()实现containsKey()方法,最简单的方法:判断getEntry()的结果是否为null
  • 因为,Entry不为null就表明存在key的映射
  • 编写JDK源码的大佬,也是这样想的:
public boolean containsKey(Object key) {
   return getEntry(key) != null;
}

3.3 put方法

  • 上述两个方法都是常用的查找方法,现在来看看新增节点的put()方法

向基于红黑树的TreeMap中新增节点,可能存在以下情况:

  • 红黑树为空:

    • 新增节点将成为根节点,红黑树节点数、结构发生变化
    • 返回的oldValue应该为null,因为之前不存在映射的value)
  • 红黑树中存在键为key的Entry:

    • 根据key的自然顺序或自定义的比较器搜索红黑树,发现存在键为key的Entry
    • 更新Entry的value并返回oldValue
  • 红黑树中不存在键为key的Entry:

    • 搜索红黑树时,一直搜索到null节点,说明不存在键为key的Entry
    • null节点的位置插入新增节点,调整红黑树,红黑树的节点数、结构均发生变化
    • 返回的oldValue为null,因为之前不存在映射的value
  • 源代码如下:

    public V put(K key, V value) {
        Entry<K,V> t = root;
        // 红黑树为空,新增节点作根节点
        if (t == null) {
            // 巧妙借助compare方法,检查key是否为null、类型是否正确(兼容)
            compare(key, key); // type (and possibly null) check
    		// 根节点的父节点为null
            root = new Entry<>(key, value, null);
            // 红黑树的结构有所变化
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        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); // 更新value并返回oldValue
            } while (t != null);
        }
        else { // 使用key的自然顺序搜索红黑树
        	// 先检查key是否为null,若为null,无法进行后续比较
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        // 未找到已存在的映射,需要在null节点处插入新增节点
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        // 新增节点为红色,需要调整红黑树的结构
        fixAfterInsertion(e);
        // 新增节点,红黑树size和结构均发生变化
        size++;
        modCount++;
        return null;
    }
    
  • 其中compare()方法,自认为叫做check方法更为恰当。它负责检查key的类型是否正确,同时顺带检查key是否为null

    final int compare(Object k1, Object k2) {
        return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
            : comparator.compare((K)k1, (K)k2);
    }
    
  • Entry的setValue()方法:更新Entry的value、返回oldValue

    public V setValue(V value) {
        V oldValue = this.value;
        this.value = value;
        return oldValue;
    }
    
  • 插入新节点后的,调用fixAfterInsertion()方法调整红黑树。该方法是对新增节点,红黑树调整的代码实现。

3.4 remove方法

3.4.1 remove方法分析

  • remove()方法:
    (1)若给定的key在map中存在映射,则删除对应的Entry并返回oldValue(可能为nulll
    (2)若给定的key在map中不存在映射,则不存在oldValue,直接返回null

  • 因此, remove()方法的逻辑也非常简单
    (1)根据key查找对应的Entry
    (2)Entry为null,直接返回null,结束操作
    (3)Entry不为null,记录oldValue,从红黑树中删除该Entry,再返回oldValue

  • remove()方法的代码如下:

    public V remove(Object key) {
        Entry<K,V> p = getEntry(key); // 查找key对应的entry
        if (p == null) 
            return null;
    
        V oldValue = p.value;
        deleteEntry(p); // 删除key对应的entry
        return oldValue;
    }
    
  • deleteEntry()remove()方法的核心:
    (1)被删除节点存在左右子节点,后继节点替代被删除节点:被删除节点的key、value更新为后继节点的key、value,后继节点成为新的被删除节点
    (2)被删除节点存在左子节点,则左子节点作为替换节点;否则,右子节点作为替换节点(后继节点的选择,使得被删除节点不可能同时存在左右子节点)
    (3)替换节点不为null:将被删除节点的父节点与替换节点做关联;将被删除节点变成孤立的节点;被删除节点颜色为黑色,则会影响红黑树的黑色高度,需要进行删除调整
    (4)父节点为null,说明被删除节点是根节点,直接将根节点置为null
    (5)不存在替换节点、也不是根节点,说明被删除节点是叶子节点;被删除节点为黑色,从被删除节点开始进行删除调整;断开被删除节点与其父节点的关联

    private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;
    
        // 存在左右子节点,查找后继节点更新被删除节点(更新key、value和指向)
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children
    
        // 从替换节点开始,做删除调整
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);
    	// 替换节点不为null,替换节点代替被删除节点;被删除节点为黑色,需要从替换节点开始做删除调整
        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;
    
            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;
    
            // Fix replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // 被删除节点是根节点,直接返回null(整棵树被删除)
            root = null;
        } else { // 被删除节点是叶子节点,从自身开始删除调整并断开与父节点的关联
            if (p.color == BLACK)
                fixAfterDeletion(p);
    
            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }
    

3.4.2 后继节点

  • 之前,学习二叉搜索树时,删除节点采用的是leetcode上的方法:
    • 被删除节点是叶子节点,直接删除
    • 被删除节点存在右子树,后继节点上提,递归删除右子树中的后继节点
    • 本删除节点只存在左子树,前驱节点上提,递归删除左子树中的前驱节点
  • 后继节点:右子树中的最左节点,前驱节点:左子树中的最右节点
  • 在JDK源码中,前驱节点与后继节点是严格定义的:按某种次序遍历时,该节点的前一个或后一个节点
  • 红黑树是一个近似平衡的二叉搜索树,具备中序遍历为升序的特性,即 v a l ( 前 驱 节 点 ) < v a l ( 当 前 节 点 ) < v a l ( 后 继 节 点 ) val (前驱节点) < val(当前节点) < val(后继节点) val()<val()<val

后继节点的定义:

  1. 右子树不为空,则后继节点为右子树中的最左节点
  2. 右子树为空,则沿父节点向左上查找,第一个向右拐的祖先节点
  3. 注意: 沿父节点向左上查找,即遍历父节点的右子节点
  • 示意图如下,该树中序遍历的结果为:9, 18, 21, 30, 35, 45, 50, 60, 64, 69, 90

  • 节点35的后继节点为45,与按照上述定义查找的后继节点一致

  • 节点30的后继节点为35,与按照上述定义查找的后继节点一致
    在这里插入图片描述

  • successor()方法,查找后继节点

    static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
        if (t == null) // 当前节点为null,无后继节点
            return null;
        else if (t.right != null) { // 存在右子树,后继节点为右子树的最左节点
            Entry<K,V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;
        } else { // 不存在右子树,后继节为点左上第一个向右拐的祖先节点
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }
    

3.4.3 前驱节点

  • 知道了后继节点的定义,则前驱节点的也可以照葫芦画瓢

前驱节点的定义

  1. 左子树不为空,则前驱节点为左子树的最右节点
  2. 左子树为空,沿父节点向右上查找,第一个向左拐的节点为前驱节点
  3. 注意: 沿父节点向右上查找,即遍历父节点的左子节点
  • 寻找及节点35、45的前驱节点,示意图如下,具体分析过程不再赘述
    在这里插入图片描述

  • predecessor()方法的代码如下

    static <K,V> Entry<K,V> predecessor(Entry<K,V> t) {
        if (t == null) // 当前节点为null,无前驱节点
            return null;
        else if (t.left != null) { // 存在左子树,前驱节点为左子树的最右节点
            Entry<K,V> p = t.left;
            while (p.right != null)
                p = p.right;
            return p;
        } else { // 不存在左子树,沿父节点向右上查找,第一个向左拐的节点
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.left) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }
    

3.5 重要方法的总结

  • TreeMap底层使用红黑树,其查找、删除、新增节点的操作,时间复杂度都是 O ( l o g 2 N ) O(log_2N) O(log2N)
  • get()方法:获取映射到key的value;内部核心方法:getEntry()获取key的Entry,分为按照给定的比较器或key的自然顺序进行查找
  • containsKey()方法:判断map中是否存在key的映射;内部核心方法:getEntry()获取key的Entry
  • put()方法:向map中添加key-value,存在三种情况:(1)红黑树为空,新增Entry将作为根节点,直接返回null;(2)存在key的映射:查找到对应的Entry后,更新value并返回oldValue;(3)不存在key的映射:插入节点,调用fixAfterInsertion()方法对其进行插入调整
  • remove()方法:通过getEntry()获取key的Entry,然后调用deleteEntry()删除该Entry;
  • deleteEntry()的核心思想:
    (1)存在右子子树,将后继节点上提,从后继节点开始进行删除操作
    (2)获取替换节点:左子节点不为空,选择左子节点作替换节点;否则,选择右子节点作替换节点
    (3)替换节点不为空:替换节点代替被删除节点,孤立被删除节点,根据颜色决定是否进行删除调整(fixAfterDeletion()方法)
    (4)被删除节点为根节点,直接将根节点置为null
    (5)被删除节点为叶子节点:根据颜色决定是否进行删除调整,然后孤立被删除节点
  • TreeMap中的前驱节点与后继节点都是严格定义的
    • 后继节点:右子树的最左节点,或者左上第一个右拐的祖先节点
    • 前驱节点:左子树的最右节点,或右上第一个左拐的祖先节点

参考文档

4. TreeMap的实际应用

  • TreeSet是基于TreeMap实现的
  • 想要map遍历的结果是有序的,可以使用TreeMap(说实话,自己好像真的没有使用过的TreeMap 😂)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值