TreeMap 的排序及比较器问题

本贴最后更新于 3056 天前,其中的信息可能已经事过境迁

   TreeMap 默认按键的自然顺序升序进行排序,如果有需求要按键的倒序排序,或者按值类型进行排序呢?
   在问题开始之前,让我们先回顾一下有关 Map 及其排序基本的知识点

  1. 用的最多的 HashMap,不保证映射的顺序,特别是它不保证该顺序恒久不变。
  2. LinkedHashMap,维持元素的插入顺序。
  3. TreeMap 中有一个传入比较器的构造函数, Map 中的元素可按此比较器进行排序。

   以上 3 个知识点,前 2 个作为复习,最后一个才是本次使用的重点。要想改变 TreeMap 的默认比较次序,我们可以在其构造函数中传入一个自己的比较器。TreeMap 的比较器构造函数如下:

public TreeMap(Comparator<? super K> comparator)

Comaprator 排序接口定义如下:

public interface Comparator<T> { int compare(T o1, T o2); ....... //若干方法 }

Comparator 接口必须实现 compare()方法。返回的 int 值的正负表示两值的大小。本着先易后难原则,让我们先实现 TreeMap 按键倒序排序:

package top.wthfeng.hello; import java.util.Comparator; import java.util.Map; import java.util.TreeMap; public class Map2Test{ public static void main(String[]args){ Map<String,String> map = new TreeMap<>(new Comparator<String>(){ public int compare(String o1,String o2){ return o2.compareTo(o1); //用正负表示大小值 } }); //以上4行可用下面一行lambda表达式代替 //Map<String,String> map1 = new TreeMap<>((o1,o2)->o2.compareTo(o1)); map.put("zdef","rfgh"); map.put("asrg","zfg"); map.put("rgd","dfgh"); map.put("cbf","gddf"); for(Map.Entry<String,String> entry:map.entrySet()){ System.out.println("key:"+entry.getKey()+",:value:"+entry.getValue()); } } } //输出结果(倒序): key:zdef,:value:rfgh key:rgd,:value:dfgh key:cbf,:value:gddf key:asrg,:value:zfg

   在 TreeMap 的构造函数中传入实现了 Comparator 接口的类实例(本例以内部匿名类实现,道理都一样,匿名类更简单,当然 java8 以后更推荐使用 lambda 用法),该类的唯一方法 comparaTo()实现了比较算法。这样 TreeMap 的倒序排列就解决了。下面我们来研究 TreeMap 的按值排序。
   先想想思路,map 的按值排序是没有现成方法的,这里就要变换一下想法。在集合工具类 Collections 中有对集合进行排序的方法,还可传入一个比较器按比较器进行排序。方法签名如下:

public static <T> void sort(List<T> list,Comparator<? super T> c)

   这也就是说要是一个 list 的话,就像上面一样给传一个比较器,再调用 Collections.sort()方法就能解决,可这是 map 啊,那能不能将 map 转为 list 啊?直接看下面吧:

package top.wthfeng.hello; import java.util.*; public class MapTest{ public static void main(String[]args){ Map<String,String> map = new TreeMap<>(); map.put("zdef","rfgh"); map.put("asrg","zfg"); map.put("rgd","dfgh"); map.put("cbf","gddf"); //将Map转为List List<Map.Entry<String,String>> list = new ArrayList<>(map.entrySet()); Collections.sort(list, new Comparator<Map.Entry<String, String>>() { public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) { return o2.getValue().compareTo(o1.getValue()); } }); //重新排序 //运用lambda表达式 //Collections.sort(list,((o1, o2) -> o2.getValue().compareTo(o1.getValue()))); for(Map.Entry<String,String> entry:list){ System.out.println("key:"+entry.getKey()+",:value:"+entry.getValue()); } } } //输出(按值倒序) key:asrg,:value:zfg key:zdef,:value:rfgh key:cbf,:value:gddf key:rgd,:value:dfgh

   OK,TreeMap 的按值排序就这样解决了。让我们总结一下,以上 2 个例子用到了 Comparator 这个接口,而这个接口到底是个怎样的存在呢?

强行对某个对象 collection 进行整体排序 的比较函数。可以使用 Comparator 来控制某些数据结构(如有序 set 或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。

   以上是 Java API6 上的说明,我又参考了其他资料,大致可以认为:是为那些没有排序方法的类自定义一个排序方法的一种手段,由于和原数据类没有耦合,又称之为外部比较器。比如说你写了一个苹果类,Apple 有 weight 属性。现要将 Apple 以 weight 升序放到 list 中,那么你就可以像上面那样,写个类实现 Comparator,在 Collections.sort()中实现排序。

package top.wthfeng.hello.test; /** * 苹果类 */ public class Apple { /** * 重量 */ private Integer weight; /** * 价格 */ private Integer price; public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } public Integer getWeight() { return weight; } public void setWeight(Integer weight) { this.weight = weight; } @Override public String toString() { //重写toString()方法,方面输出 StringBuilder sb = new StringBuilder(); sb.append("["); sb.append("Apple:(weight:"); sb.append(weight); sb.append(",price:"); sb.append(price); sb.append(")]"); return sb.toString(); } } package top.wthfeng.hello; import top.wthfeng.hello.test.Apple; import java.util.*; public class Test { //测试类 public static void main(String[] args) { List<Apple> apples = new ArrayList<>(); Random random = new Random(12); for (int i = 0; i < 10; i++) { //生成10个苹果,重量随机生成 Apple apple = new Apple(); apple.setWeight(random.nextInt(1000)); apples.add(apple); } for (Apple apple : apples) { //打印10个苹果的顺序 System.out.println("apple = " + apple); } Collections.sort(apples, new Comparator<Apple>() { //排序,传入一个比较器 @Override public int compare(Apple o1, Apple o2) { return o1.getWeight().compareTo(o2.getWeight()); } }); // Collections.sort(apples,(o1,o2)->o1.getWeight().compareTo(o2.getWeight())); for (Apple apple : apples) { //排序后的顺序 System.out.println(" sort apple = " + apple); } } } //输出 apple = [Apple:(weight:866,price:12)] apple = [Apple:(weight:556,price:33)] apple = [Apple:(weight:624,price:11)] apple = [Apple:(weight:750,price:15)] apple = [Apple:(weight:596,price:21)] apple = [Apple:(weight:568,price:22)] apple = [Apple:(weight:61,price:7)] apple = [Apple:(weight:695,price:14)] apple = [Apple:(weight:536,price:31)] apple = [Apple:(weight:505,price:3)] sort apple = [Apple:(weight:61,price:7)] sort apple = [Apple:(weight:505,price:3)] sort apple = [Apple:(weight:536,price:31)] sort apple = [Apple:(weight:556,price:33)] sort apple = [Apple:(weight:568,price:22)] sort apple = [Apple:(weight:596,price:21)] sort apple = [Apple:(weight:624,price:11)] sort apple = [Apple:(weight:695,price:14)] sort apple = [Apple:(weight:750,price:15)] sort apple = [Apple:(weight:866,price:12)]

   按 weight 排序完成。总结一下:我们自定义的类想按某个字段排序,可以利用 Collections 的 sort 方法传入一个自定义的比较器,这种比较器与被比较的类不发生耦合,称为外部比较器。
   那么问题来了,如果我的 Apple 类默认排序是按价格,特殊情况才按重量。总不能每次排序时都要写遍比较器实现吧?这也太麻烦了。不知大家注意到没有,在实现 Comparator 接口中,都有类似下面的句子:

return o2.compareTo(o1);

   那 compareTo()方法从哪来的?o1,o2 是 String 类型,compareTo()正是 String 实现的 Comparable 接口的方法。那么 Comparable 又是什么鬼?

Comparable 接口对实现它的每个类的对象进行整体排序,这种排序称为类的自然排序,类的 compareTo 方法被称为类的比较方法。

   有点眉目了,再看看 Comparable 的解释,发现 java 中所有值类都实现了 Comparable 方法。像 String、Integer、Byte 等等。这些 java 内置的值类就是根据 compare 方法比较大小的,尤其重要的是,若类实现了 Comparable 接口,它就跟许多泛型算法及依赖于该接口的集合比较算法相关。这就是类的内部排序。

package top.wthfeng.hello.test; /** * 苹果类 */ public class Apple implements Comparable<Apple>{ /** * 重量 */ private Integer weight; /** * 价格 */ private Integer price; public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } public Integer getWeight() { return weight; } public void setWeight(Integer weight) { this.weight = weight; } @Override public String toString() { //重写toString()方法,方面输出 StringBuilder sb = new StringBuilder(); sb.append("["); sb.append("Apple:(weight:"); sb.append(weight); sb.append(",price:"); sb.append(price); sb.append(")]"); return sb.toString(); } @Override public int compareTo(Apple o) { //实现内部排序 return this.price.compareTo(o.getPrice()); } } package top.wthfeng.hello; import top.wthfeng.hello.test.Apple; import java.util.*; public class Test { public static void main(String[] args) { List<Apple> apples = new ArrayList<>(); Random random = new Random(12); for (int i = 0; i < 10; i++) { //生成10个苹果,重量随机生成 Apple apple = new Apple(); apple.setWeight(random.nextInt(1000)); apple.setPrice(random.nextInt(50)); apples.add(apple); } for (Apple apple : apples) { //打印10个苹果的顺序 System.out.println("apple = " + apple); } Collections.sort(apples); // Collections.sort(apples,(o1,o2)->o1.getWeight().compareTo(o2.getWeight())); for (Apple apple : apples) { System.out.println(" sort apple = " + apple); } } } //输出 apple = [Apple:(weight:866,price:12)] apple = [Apple:(weight:556,price:33)] apple = [Apple:(weight:624,price:11)] apple = [Apple:(weight:750,price:15)] apple = [Apple:(weight:596,price:21)] apple = [Apple:(weight:568,price:22)] apple = [Apple:(weight:61,price:7)] apple = [Apple:(weight:695,price:14)] apple = [Apple:(weight:536,price:31)] apple = [Apple:(weight:505,price:3)] sort apple = [Apple:(weight:505,price:3)] sort apple = [Apple:(weight:61,price:7)] sort apple = [Apple:(weight:624,price:11)] sort apple = [Apple:(weight:866,price:12)] sort apple = [Apple:(weight:695,price:14)] sort apple = [Apple:(weight:750,price:15)] sort apple = [Apple:(weight:596,price:21)] sort apple = [Apple:(weight:568,price:22)] sort apple = [Apple:(weight:536,price:31)] sort apple = [Apple:(weight:556,price:33)]
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3201 引用 • 8216 回帖 • 4 关注
  • 排序
    19 引用 • 16 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 一些有用的避坑指南。

    69 引用 • 93 回帖
  • 域名

    域名(Domain Name),简称域名、网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。

    44 引用 • 208 回帖 • 2 关注
  • RESTful

    一种软件架构设计风格而不是标准,提供了一组设计原则和约束条件,主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

    30 引用 • 114 回帖
  • 电影

    这是一个不能说的秘密。

    122 引用 • 608 回帖
  • FreeMarker

    FreeMarker 是一款好用且功能强大的 Java 模版引擎。

    23 引用 • 20 回帖 • 463 关注
  • 单点登录

    单点登录(Single Sign On)是目前比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    9 引用 • 25 回帖 • 4 关注
  • 招聘

    哪里都缺人,哪里都不缺人。

    188 引用 • 1057 回帖 • 2 关注
  • OnlyOffice
    4 引用 • 16 关注
  • API

    应用程序编程接口(Application Programming Interface)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

    79 引用 • 431 回帖
  • Ubuntu

    Ubuntu(友帮拓、优般图、乌班图)是一个以桌面应用为主的 Linux 操作系统,其名称来自非洲南部祖鲁语或豪萨语的“ubuntu”一词,意思是“人性”、“我的存在是因为大家的存在”,是非洲传统的一种价值观,类似华人社会的“仁爱”思想。Ubuntu 的目标在于为一般用户提供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。

    127 引用 • 169 回帖
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    29 引用 • 202 回帖 • 29 关注
  • VirtualBox

    VirtualBox 是一款开源虚拟机软件,最早由德国 Innotek 公司开发,由 Sun Microsystems 公司出品的软件,使用 Qt 编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。

    10 引用 • 2 回帖 • 15 关注
  • Firefox

    Mozilla Firefox 中文俗称“火狐”(正式缩写为 Fx 或 fx,非正式缩写为 FF),是一个开源的网页浏览器,使用 Gecko 排版引擎,支持多种操作系统,如 Windows、OSX 及 Linux 等。

    7 引用 • 30 回帖 • 382 关注
  • OneDrive
    2 引用 • 3 关注
  • Visio
    1 引用 • 2 回帖
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    336 引用 • 324 回帖 • 1 关注
  • 心情

    心是产生任何想法的源泉,心本体会陷入到对自己本体不能理解的状态中,因为心能产生任何想法,不能分出对错,不能分出自己。

    59 引用 • 369 回帖 • 1 关注
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 706 关注
  • OneNote
    1 引用 • 3 回帖
  • Telegram

    Telegram 是一个非盈利性、基于云端的即时消息服务。它提供了支持各大操作系统平台的开源的客户端,也提供了很多强大的 APIs 给开发者创建自己的客户端和机器人。

    5 引用 • 35 回帖 • 1 关注
  • Access
    1 引用 • 3 回帖 • 2 关注
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖 • 2 关注
  • 阿里巴巴

    阿里巴巴网络技术有限公司(简称:阿里巴巴集团)是以曾担任英语教师的马云为首的 18 人,于 1999 年在中国杭州创立,他们相信互联网能够创造公平的竞争环境,让小企业通过创新与科技扩展业务,并在参与国内或全球市场竞争时处于更有利的位置。

    43 引用 • 221 回帖 • 59 关注
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 350 关注
  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖
  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖 • 6 关注
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 675 关注