jjzjj

【Java 数据结构】TreeMap和TreeSet的介绍

程序猿教你打篮球 2023-04-21 原文


目录

1、认识 TreeMap 和 TreeSet

2、TreeMap 的主要成员变量

3、TreeMap 的主要构造方法

4、TreeMap 和 TreeSet 的元素必须可比较

5、TreeMap 和 TreeSet 关于 key 有序 

6、TreeMap 和 TreeSet 的关系 

7、总结


1、认识 TreeMap 和 TreeSet

TreeMap 和 TreeSet 是Java中利用搜索树实现的 Map 和 Set,它们的底层是红黑树,而红黑树是一棵近似平衡的二叉搜索树,关于红黑树相关知识后续讲解。本期主要是学会 TreeMap 和 TreeSet 的使用,以及知道他们的特点即可。


2、TreeMap 的主要成员变量

// 存储传入比较器的引用
private final Comparator<? super K> comparator;

// 搜索树的根节点
private transient Entry<K,V> root;

// 节点个数
private transient int size = 0;

// 统计搜索树结构修改的次数
private transient int modCount = 0;

这里我们需要注意的是 comparator 这个引用,它是用来接收一个比较器的,主要功能后续会讲解,这里注意一下即可。


3、TreeMap 的主要构造方法

public TreeMap() {
    comparator = null;
}

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

public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    putAll(m);
}
  • 第一个构造方法,没什么意外的,你没传比较器嘛,自然就是 null。
  • 第二个是传了比较器的构造方法, 指定了比较器也很简单。
  • 第三个构造方法, 则是把你传递的 Map 构造一个新的树映射,包含与给定映射相同的映射,并根据其 key 的自然顺序进行排序,这些 key,必须可相互比较!

4、TreeMap 和 TreeSet 的元素必须可比较

因为 TreeMap 和 TreeSet 实现了 SortedSet 接口,表示是一个需要实现排序功能的 Map 或 Set,那实现排序的前提,你放入的元素必须是可比较的,那么也就是说,当你往 TreeMap 里面放 key 的时候,这个 key 必须可比较,也就是重写了 compaerTo 方法,你也可也直接传一个比较器也是可以的。

public class Test {
    public static void main(String[] args) {
        Map<Person, Integer> map = new TreeMap<>();
        System.out.println(map);
    }
}

这个报错就是在说, Person 无法被转换成 Comparable,也就是在 TreeMap 底层实现中无法将 key 对象中的 compareTo 方法。

具体我们还是要去看 TreeMap 的无参构造方法以及 put 的源码:

public TreeMap() {
    comparator = null;
}

对于 TreeMap 的无参构造方法,其实很简单,如果你没有传比较器进去,默认就是 null 的,接下来就得看一下 put 方法了。

源码中比较多,我们这里只截取一小部分,能明白为啥重写 compareTo 方法即可。

很明显,我们是第一次 put 元素,所以搜索树的根节点也就是 root 节点为 null,接着将我们 put 进去的 key 作为 compare 两个参数传递进去,接着去看 compare 的实现:

这里我们没有传比较器,所以刚开始的无参构造方法已经将 comparator 置 null了,很明显这里可以发现,如果我们传了比较器,就按照比较器的方式来比较,如果没有比较器,则会将 key 转换成 Comparable<Person>,这个尖括号中的类型, 此时我们这里是转换成 Person,这也是泛型上界的相关知识,所以即最终调用了我们 key 对象中的 compareTo 方法,那如果我们没有实现 Comparable 这个接口,也没有重写 compareTo 方法自然会抛异常。

当然后续 put 元素也是按照上面的方法来比较的,有比较器,使用比较器来比较,无比较器,调用对象中的 compareTo 进行比较。


5、TreeMap 和 TreeSet 关于 key 有序 

我们前面讲到过, TreeMap 和 TreeSet 的底层其实是搜索树,而且是红黑树,那么中序遍历搜索树是有序的,也即按照 key 提供的比较方式,或者你自己提供的比较器,关于 key 是有序的。

class Person implements Comparable<Person> {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }
}

public class Test {
    public static void main(String[] args) {
        TreeMap<Person, Integer> map = new TreeMap<>();
        map.put(new Person("张三", 12), 1);
        map.put(new Person("李四", 21), 2);
        map.put(new Person("王五", 16), 3);
        Set<Map.Entry<Person, Integer>> entrySet = map.entrySet();
        for (Map.Entry<Person, Integer> personIntegerEntry : entrySet) {
            System.out.println(personIntegerEntry);
        }
    }
}

由于 Map 没有继承于Iterable 接口,所以不能采用 for-each 遍历,只能返回 Key-value 的映射关系放入 Set 中进行遍历。

上述代码是按照 Person 中的年龄进行比较的,所以如果最终打印出来的结果是 12 21 16 这样的年龄排序的顺序话,也足以说明,在 TreeMap 和 TreeSet 中是关于 key 有序的,打印结果:

看到这,可能有的小伙伴说,确实是关于 key 有序,但是怎么保证底层是一棵搜索树呢?其实也很简单验证,搜索树前几期也讲过,如果是按照我们 Person 类比较的方式的话,那么根节点的左边都小于它,根节点的右边都大于它,这里我们通过调试看看 map 中存储的结构就ok了:

这里我们通过调试的方式进入到源码中,输入我们想观察的变量,于是可以看到,确实是一棵搜索树,而且不是简单的二叉搜索树,是一棵红黑树。

为什么是这里就能看出来是红黑树呢,因为如果是按照我们前面讲二叉搜索树的逻辑, 根节点一定是第一次插入的 “张三,12”,而这里跟节点为什么是 “王五,16”,因为当插入 “王五” 的时候,搜索树的左右子树高度不平衡了,红黑树进行了旋转调整,至于更多底层实现细节,目前我们可以不用关心,由此也可能看出来并不是一棵简单的二叉搜索树。


6、TreeMap 和 TreeSet 的关系 

上期其实也提到过,Set 的底层其实就是 Map,而 TreeSet 也是一样,底层仍然是 TreeMap,拿什么证明呢?其实我们来看 Set 的构造方法就可以了:

TreeSet(NavigableMap<E,Object> m) {
    this.m = m;
}

public TreeSet() {
    this(new TreeMap<E,Object>());
}

public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

public TreeSet(Collection<? extends E> c) {
    this();
    addAll(c);
}

这里的 NavigableMap 也是一个继承 SortedMap 的接口,因此具有SortedMap,Map接口的属性方法,通过上述的构造方法也能看出,当你实例化一个 TreeSet 对象的时候,本质上还是 new 了一个 TreeMap 对象。而能明显看到,value 为一个 Object 默认对象。


7、总结

TreeMap 和 TreeSet 底层都是红黑树,插入删除查找的时间复杂度为 O(logN),数据关于 key 是有序的,key 必须要能够比较,不然会抛出 ClassCastException 异常,主要运用于需要 key 有序的场景下,TreeMap 和 TreeSet 是线程不安全的。 


下期预告:【Java 数据结构】HashMap和HashSet

有关【Java 数据结构】TreeMap和TreeSet的介绍的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  4. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  5. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  6. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  7. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  8. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  9. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  10. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

随机推荐