前言
TreeMap的基本概念:

TreeMap集合是基于红黑树(Red-Black tree)的 NavigableMap实现。该集合最重要的特点就是可排序,该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。这句话是什么意思呢?就是说TreeMap可以对添加进来的元素进行排序,可以按照默认的排序方式,也可以自己指定排序方式。

根据上一条,我们要想使用TreeMap存储并排序我们自定义的类(如User类),那么必须自己定义比较机制:一种方式是User类去实现java.lang.Comparable接口,并实现其compareTo()方法。另一种方式是写一个类(如MyCompatator)去实现java.util.Comparator接口,并实现compare()方法,然后将MyCompatator类实例对象作为TreeMap的构造方法参数进行传参(当然也可以使用匿名内部类),这些比较方法是怎么被调用的将在源码中讲解。

下图是Map集合体系类图。

正文
TreeMap源码分析

1,类名及类成员变量

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
    // 比较器对象
    private final Comparator<? super K> comparator;

// 根节点
    private transient Entry<K,V> root;

// 集合大小
    private transient int size = 0;

// 树结构被修改的次数
    private transient int modCount = 0;

// 静态内部类用来表示节点类型
    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;     // 键
        V value;   // 值
        Entry<K,V> left;    // 指向左子树的引用(指针)
        Entry<K,V> right;   // 指向右子树的引用(指针)
        Entry<K,V> parent;  // 指向父节点的引用(指针)
        boolean color = BLACK; // 
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2,类构造方法

public TreeMap() {   // 1,无参构造方法
        comparator = null; // 默认比较机制
    }

public TreeMap(Comparator<? super K> comparator) { // 2,自定义比较器的构造方法
        this.comparator = comparator;
    }

public TreeMap(Map<? extends K, ? extends V> m) {  // 3,构造已知Map对象为TreeMap
        comparator = null; // 默认比较机制
        putAll(m);
    }

public TreeMap(SortedMap<K, ? extends V> m) { // 4,构造已知的SortedMap对象为TreeMap
        comparator = m.comparator(); // 使用已知对象的构造器
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
3,put()方法详解

public V put(K key, V value) {
        Entry<K,V> t = root;  // 获取根节点

// 如果根节点为空,则该元素置为根节点 
        if (t == null) {
            compare(key, key); // type (and possibly null) check

root = new Entry<>(key, value, null);
            size = 1;    // 集合大小为1
            modCount++;  // 结构修改次数自增
            return null;
        }

int cmp;
        Entry<K,V> parent;
        Comparator<? super K> cpr = comparator;  // 比较器对象

// 如果比较器对象不为空,也就是自定义了比较器
        if (cpr != null) {   
            do { // 循环比较并确定元素应插入的位置(也就是找到该元素的父节点)
                parent = t;  // t就是root

// 调用比较器对象的compare()方法,该方法返回一个整数
                cmp = cpr.compare(key, t.key); 
                if (cmp < 0)      // 待插入元素的key"小于"当前位置元素的key,则查询左子树
                    t = t.left;
                else if (cmp > 0) // 待插入元素的key"大于"当前位置元素的key,则查询右子树
                    t = t.right;
                else              // "相等"则替换其value。
                    return t.setValue(value);
            } while (t != null);
        }

// 如果比较器对象为空,使用默认的比较机制
        else {
            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)       // 待插入元素的key"小于"当前位置元素的key,则查询左子树
                    t = t.left;
                else if (cmp > 0)  // 待插入元素的key"大于"当前位置元素的key,则查询右子树
                    t = t.right;
                else               // "相等"则替换其value。
                    return t.setValue(value);
            } while (t != null);
        }

Entry<K,V> e = new Entry<>(key, value, parent);  // 根据key找到父节点后新建一个节点
        if (cmp < 0)  // 根据比较的结果来确定放在左子树还是右子树
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;      // 集合大小+1
        modCount++;  // 集合结构被修改次数+1
        return null;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
3.1,自定义比较器的使用。说了这么多关于比较器的内容,不上手试试这么能行?

先来看下面这段代码
package com.jimmy.map;

import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

public class TreeMapDemo2 {
    public static void main(String[] args) {
        Map<String, String> map = new TreeMap<>();

map.put("ddd", "444");
        map.put("ccc", "333");
        map.put("bbb", "222");
        map.put("aaa", "111");

Set<Entry<String, String>> entrySet = map.entrySet();
        for (Entry<String, String> each : entrySet) {
            System.out.println(each.getKey()+"::"+each.getValue());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
输出结果如下,结果是排序过的,为什么呢?那是因为String类实现了Comparable接口并实现了compareTo()方法,该方法按字典顺序比较两个字符串,请自行查看其实现。

aaa::111
bbb::222
ccc::333
ddd::444
1
2
3
4
下面我们写个自定义User类,使用2种方式将类对象按照age字段从小到大排序。
方式1,User实现Comparable接口并实现了compareTo()方法

User类

package com.jimmy.domain;

public class User implements Comparable<User>{
    private String username;
    private int age;

public User(String username, int age) {
        this.username = username;
        this.age = age;
    }

@Override
    public String toString() {
        return "User [username=" + username + ", age=" + age + "]";
    }
    @Override
    public int compareTo(User user) {
        int temp = this.age - user.age;
        return temp == 0 ? this.username.compareTo(user.username) : temp;
    }   
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
测试代码

package com.jimmy.map;

import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import com.jimmy.domain.User;

public class TreeMapDemo1 {
    public static void main(String[] args) {
        Map<User, String> map = new TreeMap<>();

map.put(new User("jimmy1", 30), "hello");
        map.put(new User("jimmy2", 30), "hello");
        map.put(new User("jimmy", 22), "hello");
        map.put(new User("jimmy", 20), "hello");

Set<Entry<User, String>> entrySet = map.entrySet();
        for (Entry<User, String> each : entrySet) {
            System.out.println(each.getKey()+"::"+each.getValue());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
输出结果如下,首先按age排序,若年龄相等则再按username的字母表顺序排序。

User [username=jimmy, age=20]::hello
User [username=jimmy, age=22]::hello
User [username=jimmy1, age=30]::hello
User [username=jimmy2, age=30]::hello
1
2
3
4
方式2,写一个类实现java.util.Comparator接口,并将该类对象传递给TreeMap的构造方法。这种方式将实体类和比较机制解耦合,可以写很多个不同的比较器对象。

实体类

package com.jimmy.domain;

public class User3 {  // User对象不再实现任何接口
    private String username;
    private int age;

public User3(String username, int age) {
        super();
        this.username = username;
        this.age = age;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User3 [username=" + username + ", age=" + age + "]";
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
比较器类

package com.jimmy.map;

import java.util.Comparator;
import com.jimmy.domain.User3;

public class TreeMapComparator implements Comparator<User3>{  // 比较器类

@Override
    public int compare(User3 o1, User3 o2) {
        int temp = o1.getAge() - o2.getAge();
        return temp == 0 ? o1.getUsername().compareTo(o2.getUsername()) : temp;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
测试代码

package com.jimmy.map;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;

import com.jimmy.domain.User3;

public class TreeMapDemo3 {
    public static void main(String[] args) {
        Map<User3, String> map = new TreeMap<>(new TreeMapComparator());

map.put(new User3("jimmy1", 30), "hello");
        map.put(new User3("jimmy2", 30), "hello");
        map.put(new User3("jimmy", 22), "hello");
        map.put(new User3("jimmy", 20), "hello");

Set<Entry<User3, String>> entrySet = map.entrySet();
        for (Entry<User3, String> each : entrySet) {
            System.out.println(each.getKey()+"::"+each.getValue());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
输出结果如下,跟上面的相同。

User3 [username=jimmy, age=20]::hello
User3 [username=jimmy, age=22]::hello
User3 [username=jimmy1, age=30]::hello
User3 [username=jimmy2, age=30]::hello
1
2
3
4
当然,我们还可以不写比较器类,而是使用匿名内部类的形式来写比较器。

package com.jimmy.map;

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

import com.jimmy.domain.User3;

public class TreeMapDemo4 {
    public static void main(String[] args) {
        Map<User3, String> map = new TreeMap<>(new Comparator<User3>() {

@Override
            public int compare(User3 o1, User3 o2) {
                int temp = o1.getAge() - o2.getAge();
                return temp == 0 ? o1.getUsername().compareTo(o2.getUsername()) : temp;
            }
        });

map.put(new User3("jimmy1", 30), "hello");
        map.put(new User3("jimmy2", 30), "hello");
        map.put(new User3("jimmy", 22), "hello");
        map.put(new User3("jimmy", 20), "hello");

Set<Entry<User3, String>> entrySet = map.entrySet();
        for (Entry<User3, String> each : entrySet) {
            System.out.println(each.getKey()+"::"+each.getValue());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
4,一帮以getEntry()方法为基础的获取元素的方法,其中包括containsKey(),get(),remove()等。

final Entry<K,V> getEntry(Object key) {
        // 如果有自定义比较器对象,就按照自定义规则遍历二叉树
        if (comparator != null)
            return getEntryUsingComparator(key);
        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;
        }
        return null;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5,一帮以getFirstEntry(),getLastEntry()为基础的获取头和尾元素的方法,其中包括:firstKey(),lastKey();firstEntry(),lastEntry();pollFirstEntry(),pollLastEntry();

final Entry<K,V> getFirstEntry() { // 获取第一个元素也就是最小的元素,一直遍历左子树
        Entry<K,V> p = root;
        if (p != null)
            while (p.left != null)
                p = p.left;
        return p;
    }

final Entry<K,V> getLastEntry() { // 获取最后个元素也就是最大的元素,一直遍历右子树
        Entry<K,V> p = root;
        if (p != null)
            while (p.right != null)
                p = p.right;
        return p;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
6,keySet()和entrySet()方法,在将HashMap的时候已经讲过了,Map没有迭代器,要将Map转化为Set,用Set的迭代器才能进行元素迭代。

总结
TreeMap继承了Map的性质,同时其树结构又可以进行元素排序,用处很大。
--------------------- 
作者:name_s_Jimmy 
来源:CSDN 
原文:https://blog.csdn.net/qq_32166627/article/details/72773293 
版权声明:本文为博主原创文章,转载请附上博文链接!

java集合(6):TreeMap源码分析(jdk1.8)相关推荐

  1. 死磕 java集合之TreeMap源码分析(一)——红黑树全解析

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历. 继承体系 Tr ...

  2. 死磕 java集合之TreeMap源码分析(一)- 内含红黑树分析全过程

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历. 继承体系 Tr ...

  3. 死磕 java集合之TreeMap源码分析(二)- 内含红黑树分析全过程

    2019独角兽企业重金招聘Python工程师标准>>> 欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 插入元素 插入元素, ...

  4. 死磕 java集合之TreeMap源码分析(三)- 内含红黑树分析全过程

    2019独角兽企业重金招聘Python工程师标准>>> 欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 删除元素 删除元素本 ...

  5. Java集合之TreeMap源码解析上篇

    上期回顾 上期我从树型结构谈到了红黑树的概念以及自平衡的各种变化(指路上期←戳),本期我将会对TreeMap结合红黑树理论进行解读. 首先,我们先来回忆一下红黑树的5条基本规则. 1.结点是红色或者黑 ...

  6. 死磕 java集合之ArrayDeque源码分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...

  7. 【死磕 Java 集合】— LinkedTransferQueue源码分析

    [死磕 Java 集合]- LinkedTransferQueue源码分析 问题 (1)LinkedTransferQueue是什么东东? (2)LinkedTransferQueue是怎么实现阻塞队 ...

  8. 死磕Java集合之BitSet源码分析(JDK18)

    死磕Java集合之BitSet源码分析(JDK18) 文章目录 死磕Java集合之BitSet源码分析(JDK18) 简介 继承体系 存储结构 源码解析 属性 构造方法 set(int bitInde ...

  9. java arraydeque_死磕 java集合之ArrayDeque源码分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...

  10. Java集合:Hashtable源码分析

    1. 概述 上次讨论了HashMap的结构,原理和实现,本文来对Map家族的另外一个常用集合HashTable进行介绍.HashTable和HashMap两种集合非常相似,经常被各种面试官问到两者的区 ...

最新文章

  1. python numpy转字符串
  2. JMF介绍之媒体框架二
  3. python Hbase Thrift pycharm 及引入包
  4. UpdateLayeredWindow 绘制异型窗口
  5. python3反转字符串的3种方法
  6. 库函数和系统调用的区别
  7. django orm 之makemigrations和migrate命令
  8. 受宠的背后:安全市场面临重新洗牌
  9. c语言标准版表白代码教程,C语言告白代码,一闪一闪亮晶晶~
  10. 使用TreeMap对要签名做排序ASCII码排序
  11. java中lookup函数怎么用,excel Lookup查表函数的使用方法
  12. istio 防故障流量控制
  13. spring boot 配置文件properties,yml语法学习及属性获取@ConfigurationProperties和@Value
  14. Python分析捕食者和被捕食者模型 Lotka--Volterra方程 | 拟合求解a,b,c,d
  15. Web端的邮件内容HTML格式规范总结
  16. python制作壁纸获取器exe,壁纸采集
  17. 手机计算机藏应用,手机“计算器”隐藏功能,一键把隐私照片加密
  18. Java泛型方法与普通成员方法以及案例说明(五)
  19. SELinux avc权限--audit2allow
  20. 开发amis工作日历组件

热门文章

  1. java.sql.SQLException: Lock wait timeout exceeded --转
  2. AOP 的利器:ASM 3.0 介绍
  3. LESSON 10.110.210.3 SSE与二分类交叉熵损失函数二分类交叉熵损失函数的pytorch实现多分类交叉熵损失函数
  4. IMF 报告:比特币等加密货币有朝一日可能取代传统支付手段
  5. 如何快速全面建立自己的大数据知识体系? 大数据 ETL 用户画像 机器学习 阅读232 作者经过研发多个大数据产品,将自己形成关于大数据知识体系的干货分享出来,希望给大家能够快速建立起大数据
  6. 实现主成分分析和白化
  7. jvm性能调优实战 - 29使用 jstat 摸清线上系统的JVM运行状况
  8. MySQL-索引优化篇(3)_利用索引优化锁
  9. Spring-Spring4.X 概述
  10. Translucent System Bars-4.4新特性