0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

JDK中java.util.TreeMap 类的介绍

科技绿洲 来源:Java技术指北 作者:Java技术指北 2023-10-10 11:45 次阅读

本篇文章给大家介绍基于树实现的数据结构——TreeMap

1、TreeMap 定义

听名字就知道,TreeMap 是由Tree 和 Map 集合有关的,没错,TreeMap 是由红黑树实现的有序的 key-value 集合。

PS:想要学懂TreeMap的实现原理,红黑树的了解是必不可少的!!!

public class TreeMap< K,V >
    extends AbstractMap< K,V >
    implements NavigableMap< K,V >, Cloneable, java.io.Serializable

图片

TreeMap 首先继承了 AbstractMap 抽象类,表示它具有散列表的性质,也就是由 key-value 组成。

其次 TreeMap 实现了 NavigableMap 接口,该接口支持一系列获取指定集合的导航方法,比如获取小于指定key的集合。

最后分别实现 Serializable 接口以及 Cloneable 接口,分别表示支持对象序列化以及对象克隆。

2、字段定义

①、Comparator

/**
     * The comparator used to maintain order in this tree map, or
     * null if it uses the natural ordering of its keys.
     *
     * @serial
     */
    private final Comparator< ? super K > comparator;

可以看上面的英文注释,Comparator 是用来维护treemap集合中的顺序,如果为null,则按照key的自然顺序。

Comparator 是一个接口,排序时需要实现其 compare 方法,该方法返回正数,零,负数分别代表大于,等于,小于。那么怎么使用呢?这里举个例子:

这里有一个Person类,里面有两个属性pname,page,我们将该person对象放入ArrayList集合时,需要对其按照年龄进行排序。

package com.ys.test;

/**
 * Create by YSOcean
 */
public class Person {
    private String pname;
    private Integer page;

    public Person() {
    }

    public Person(String pname, Integer page) {
        this.pname = pname;
        this.page = page;
    }

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public Integer getPage() {
        return page;
    }

    public void setPage(Integer page) {
        this.page = page;
    }

    @Override
    public String toString() {
        return "Person{" +
                "pname='" + pname + ''' +
                ", page=" + page +
                '}';
    }
}

打印结果为:

图片

②、Entry

private transient Entry< K,V > root;

对于Entry详细源码这里不列举了,主要看几个字段:

K key;
V value;
Entry K,V > left;
Entry K,V > right;
Entry K,V > parent;
boolean color = BLACK;

相信对红黑树这种数据结构了解的人,一看这几个字段就明白了,这也印证了前面所说的TreeMap底层有红黑树这种数据结构。

③、size

/**
     * The number of entries in the tree
     */
    private transient int size = 0;

用来表示entry的个数,也就是key-value的个数。

④、modCount

/**
     * The number of structural modifications to the tree.
     */
    private transient int modCount = 0;

基本上前面讲的在ArrayList,LinkedList,HashMap等线程不安全的集合都有此字段,用来实现Fail-Fast 机制,如果在迭代这些集合的过程中,有其他线程修改了这些集合,就会抛出ConcurrentModificationException异常。

⑤、红黑树常量

private static final boolean RED   = false;
    private static final boolean BLACK = true;

3、构造函数

①、无参构造函数

1     public TreeMap() {
2         comparator = null;
3     }

比较器 comparator 置为 null,表示按照key的自然顺序进行排序。

②、带比较器的构造函数

1     public TreeMap(Comparator< ? super K > comparator) {
2         this.comparator = comparator;
3     }

需要自己实现Comparator。

③、构造包含指定map集合的元素

1     public TreeMap(Map< ? extends K, ? extends V > m) {
2         comparator = null;
3         putAll(m);
4     }

使用该构造器创建的TreeMap,会默认插入m表示的集合元素,并且comparator表示按照自然顺序进行插入。

④、带 SortedMap的构造函数

public TreeMap(SortedMap< K, ? extends V > m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

和上面带Map的构造函数不一样,map是无序的,而SortedMap 是有序的,使用 buildFromSorted() 方法将SortedMap集合中的元素插入到TreeMap 中。

4、添加元素

//添加元素
    public V put(K key, V value) {
        TreeMap.Entry< K,V > t = root;
        //如果根节点为空,即TreeMap中一个元素都没有,那么设置新添加的元素为根节点
        //并且设置集合大小size=1,以及modCount+1,这是用于快速失败
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new TreeMap.Entry<  >(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        TreeMap.Entry< K,V > parent;
        // split comparator and comparable paths
        Comparator< ? super K > cpr = comparator;
        //如果比较器不为空,即初始化TreeMap构造函数时,有传递comparator类
        //那么插入新的元素时,按照comparator实现的类进行排序
        if (cpr != null) {
            //通过do-while循环不断遍历树,调用比较器对key值进行比较
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    //遇到key相等,直接将新值覆盖到原值上
                    return t.setValue(value);
            } while (t != null);
        }
        //如果比较器为空,即初始化TreeMap构造函数时,没有传递comparator类
        //那么插入新的元素时,按照key的自然顺序
        else {
            //如果key==null,直接抛出异常
            //注意,上面构造TreeMap传入了Comparator,是可以允许key==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);
        }
        //找到父亲节点,根据父亲节点创建一个新节点
        TreeMap.Entry< K,V > e = new TreeMap.Entry<  >(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        //修正红黑树(包括节点的左旋和右旋,具体可以看我Java数据结构和算法中对红黑树的介绍)
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

添加元素,如果初始化TreeMap构造函数时,没有传递comparator类,是不允许插入key==null的键值对的,相反,如果实现了Comparator,则可以传递key=null的键值对。

另外,当插入一个新的元素后(除了根节点),会对TreeMap数据结构进行修正,也就是对红黑树进行修正,使其满足红黑树的几个特点,具体修正方法包括改变节点颜色,左旋,右旋等操作,这里我不做详细介绍了.

5、删除元素

①、根据key删除

public V remove(Object key) {
        //根据key找到该节点
        TreeMap.Entry< K,V > p = getEntry(key);
        if (p == null)
            return null;
        //获取该节点的value,并返回
        V oldValue = p.value;
        //调用deleteEntry()方法删除节点
        deleteEntry(p);
        return oldValue;
    }

    private void deleteEntry(TreeMap.Entry< K,V > p) {
        modCount++;
        size--;

        //如果删除节点的左右节点都不为空,即有两个孩子
        if (p.left != null && p.right != null) {
            //得到该节点的中序后继节点
            TreeMap.Entry< K,V > s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        TreeMap.Entry< K,V > replacement = (p.left != null ? p.left : p.right);
        //待删除节点只有一个子节点,直接删除该节点,并用该节点的唯一子节点顶替该节点
        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);

            //TreeMap中只有待删除节点P,也就是只有一个节点,直接返回nul即可
        } else if (p.parent == null) { // return if we are the only node.
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
            //待删除节点没有子节点,即为叶子节点,直接删除即可
            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;
            }
        }
    }

删除节点分为四种情况:

1、根据key没有找到该节点:也就是集合中不存在这一个节点,直接返回null即可。

2、根据key找到节点,又分为三种情况:

①、待删除节点没有子节点,即为叶子节点:直接删除该节点即可。

②、待删除节点只有一个子节点:那么首先找到待删除节点的子节点,然后删除该节点,用其唯一子节点顶替该节点。

③、待删除节点有两个子节点:首先找到该节点的中序后继节点,然后把这个后继节点的内容复制给待删除节点,然后删除该中序后继节点,删除过程又转换成前面①、②两种情况了,这里主要是找到中序后继节点,相当于待删除节点的一个替身。

6、查找元素

①、根据key查找

public V get(Object key) {
        TreeMap.Entry< K,V > p = getEntry(key);
        return (p==null ? null : p.value);
    }

    final TreeMap.Entry< K,V > getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        Comparable< ? super K > k = (Comparable< ? super K >) key;
        TreeMap.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;
    }

7、遍历元素

通常有下面两种方法,第二种方法效率要快很多。

TreeMap< String,Integer > map = new TreeMap<  >();
map.put("A",1);
map.put("B",2);
map.put("C",3);

//第一种方法
//首先利用keySet()方法得到key的集合,然后利用map.get()方法根据key得到value
Iterator< String > iterator = map.keySet().iterator();
while(iterator.hasNext()){
    String key = iterator.next();
    System.out.println(key+":"+map.get(key));
}

//第二种方法
Iterator< Map.Entry< String,Integer >> iterator1 = map.entrySet().iterator();
while(iterator1.hasNext()){
    Map.Entry< String,Integer > entry = iterator1.next();
    System.out.println(entry.getKey()+":"+entry.getValue());
}

8、小结

好了,这就是JDK中java.util.TreeMap 类的介绍。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2954

    浏览量

    104510
  • MAP
    MAP
    +关注

    关注

    0

    文章

    48

    浏览量

    15122
  • 数据结构
    +关注

    关注

    3

    文章

    572

    浏览量

    40082
  • JDK
    JDK
    +关注

    关注

    0

    文章

    81

    浏览量

    16570
收藏 人收藏

    评论

    相关推荐

    java jdk安装参考步骤

    1、把jdk-8u5-linux-x64.gz解压,然后把解压的文件夹放到/usr/lib/jvm/下,并重命名为jdk,这个目录可以自定义。2、编辑~/.basrc文件,在文件的末尾追加下面的命令
    发表于 09-25 16:43

    树莓派如何安装Java JDK

    。Oracle Java 具有其他一些商业功能,并且许可仅允许非商业用途。下面介绍如何在树莓派的 Raspbian OS 上安装Java(OpenJDK)。  运行以下命令安装最新的 JDK
    发表于 02-02 16:37

    HarmonyOS方舟开发框架容器API的介绍与使用

    对外提供。通过对存储位置以及属性的限制,让每种类型的数据都能在完成自身功能的基础上剪除冗余分支,保证了数据的高效访问,提升了应用的性能。 本期,我们将为大家介绍方舟开发框架容器的各种类型以及相关
    发表于 03-07 11:40

    看看基于JDK自带JVM工具的用法

    用到命令,下面围绕一个微服务的启动和运行,来看看基于JDK自带JVM工具的用法;三、命令行工具1、jps命令jps :虚拟机进程状态工具,该命令在Java环境部署和服务启动查看时经常用到,首先在本地
    发表于 11-16 15:30

    java jdk6.0官方下载

    java jdk6.0下载如何件: java jdk6.0安装步骤: 第一步 JDK1.6的安装步骤 第一步双击安装文件
    发表于 10-17 11:47 155次下载
    <b class='flag-5'>java</b> <b class='flag-5'>jdk</b>6.0官方下载

    java基础——java.util.ConcurrentModificationException

    本文档内容介绍java基础java.util.ConcurrentModificationException,供参考
    发表于 03-13 11:31 2次下载

    JavaArrays是什么 Arrays常用方法

    了解Arrays的概念 **A****rrays** 位于java.util包下,Arrays是一个操作数组的工具。 Arrays常用方法 Arrays.fill:
    的头像 发表于 02-17 15:11 1014次阅读
    <b class='flag-5'>Java</b><b class='flag-5'>中</b>Arrays<b class='flag-5'>类</b>是什么 Arrays常用方法

    JDKjava.util.HashSet 介绍

    JDK1.8 ,HashMap 是由 数组+链表+红黑树构成,相对于早期版本的 JDK HashMap 实现,新增了红黑树作为底层数据结构,在数据量较大且哈希碰撞较多时,能够极大的增加检索
    的头像 发表于 10-09 10:50 537次阅读
    <b class='flag-5'>JDK</b><b class='flag-5'>中</b><b class='flag-5'>java.util</b>.HashSet <b class='flag-5'>类</b>的<b class='flag-5'>介绍</b>

    Java时间转换方案

    LocalDate 的过程,我们使用 Date Java 8 新增的 toInstant() 方法进行转换。 当我们转换一个 Instant 对象时,需要使用 ZoneId
    的头像 发表于 10-09 15:48 465次阅读

    JDKjava.util.ArrayList 介绍

    AbstractList E > implements List E >, RandomAccess , Cloneable , java.io.Serializable ①、实现 RandomAccess 接口 这是
    的头像 发表于 10-10 15:51 624次阅读
    <b class='flag-5'>JDK</b><b class='flag-5'>中</b><b class='flag-5'>java.util</b>.ArrayList <b class='flag-5'>类</b>的<b class='flag-5'>介绍</b>

    JDKjava.lang.Arrays 的源码解析

    日常开发,我们会使用各种工具,利用封装好的轮子,能让我们的开发事半功倍。但是在JDK,有一个特别的工具——
    的头像 发表于 10-11 15:31 567次阅读
    <b class='flag-5'>JDK</b><b class='flag-5'>中</b><b class='flag-5'>java</b>.lang.Arrays <b class='flag-5'>类</b>的源码解析

    JDKjava.lang.String 的源码解析

    1、String 的定义 public final class String implements java.io.Serializable, Comparable, CharSequence
    的头像 发表于 10-13 10:51 450次阅读
    <b class='flag-5'>JDK</b><b class='flag-5'>中</b><b class='flag-5'>java</b>.lang.String <b class='flag-5'>类</b>的源码解析

    javautil包下有哪些

    Javautil包下,包含了许多,用于提供各种常见的实用工具和数据结构。以下是一些常见的: ArrayList:动态数组,可以根据需要自动调整大小。 LinkedList:双向
    的头像 发表于 11-22 15:04 1072次阅读

    weblogic修改jdk路径

    )路径的情况。本文将详细介绍如何在WebLogic修改JDK路径。 一、背景介绍 Java Development Kit(
    的头像 发表于 12-05 14:46 1233次阅读

    OpenHarmony语言基础库【@ohos.util.TreeMap (非线性容器TreeMap)】

    TreeMap可用于存储具有关联关系的key-value键值对集合,存储元素key值唯一,每个key对应一个value。
    的头像 发表于 04-28 15:23 261次阅读
    OpenHarmony语言基础<b class='flag-5'>类</b>库【@ohos.<b class='flag-5'>util.TreeMap</b> (非线性容器<b class='flag-5'>TreeMap</b>)】