TreeMap 的排序及比较器问题

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

   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 引用 • 8217 回帖 • 1 关注
  • 排序
    19 引用 • 16 回帖 • 1 关注

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • 代码片段

    代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。

    用户在该标签下分享代码片段时需在帖子标题前添加 [css] [js] 用于区分代码片段类型。

    202 引用 • 1453 回帖
  • CodeMirror
    2 引用 • 17 回帖 • 173 关注
  • Solidity

    Solidity 是一种智能合约高级语言,运行在 [以太坊] 虚拟机(EVM)之上。它的语法接近于 JavaScript,是一种面向对象的语言。

    3 引用 • 18 回帖 • 445 关注
  • Swift

    Swift 是苹果于 2014 年 WWDC(苹果开发者大会)发布的开发语言,可与 Objective-C 共同运行于 Mac OS 和 iOS 平台,用于搭建基于苹果平台的应用程序。

    34 引用 • 37 回帖 • 559 关注
  • Postman

    Postman 是一款简单好用的 HTTP API 调试工具。

    4 引用 • 3 回帖 • 1 关注
  • 电影

    这是一个不能说的秘密。

    123 引用 • 608 回帖
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 615 关注
  • 大数据

    大数据(big data)是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。

    89 引用 • 113 回帖
  • Solo

    Solo 是一款小而美的开源博客系统,专为程序员设计。Solo 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    1444 引用 • 10083 回帖 • 509 关注
  • 微信

    腾讯公司 2011 年 1 月 21 日推出的一款手机通讯软件。用户可以通过摇一摇、搜索号码、扫描二维码等添加好友和关注公众平台,同时可以将自己看到的精彩内容分享到微信朋友圈。

    133 引用 • 796 回帖 • 1 关注
  • 深度学习

    深度学习(Deep Learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。

    43 引用 • 44 回帖
  • Eclipse

    Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。

    76 引用 • 258 回帖 • 624 关注
  • Caddy

    Caddy 是一款默认自动启用 HTTPS 的 HTTP/2 Web 服务器。

    10 引用 • 54 回帖 • 180 关注
  • GAE

    Google App Engine(GAE)是 Google 管理的数据中心中用于 WEB 应用程序的开发和托管的平台。2008 年 4 月 发布第一个测试版本。目前支持 Python、Java 和 Go 开发部署。全球已有数十万的开发者在其上开发了众多的应用。

    14 引用 • 42 回帖 • 824 关注
  • CongSec

    本标签主要用于分享网络空间安全专业的学习笔记

    1 引用 • 1 回帖 • 37 关注
  • 资讯

    资讯是用户因为及时地获得它并利用它而能够在相对短的时间内给自己带来价值的信息,资讯有时效性和地域性。

    56 引用 • 85 回帖 • 1 关注
  • 安装

    你若安好,便是晴天。

    132 引用 • 1184 回帖 • 1 关注
  • Swagger

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

    26 引用 • 35 回帖 • 2 关注
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖
  • Outlook
    1 引用 • 5 回帖 • 2 关注
  • Docker

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的操作系统上。容器完全使用沙箱机制,几乎没有性能开销,可以很容易地在机器和数据中心中运行。

    497 引用 • 934 回帖
  • 音乐

    你听到信仰的声音了么?

    62 引用 • 512 回帖
  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    4 引用 • 16 回帖 • 195 关注
  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    284 引用 • 248 回帖
  • 博客

    记录并分享人生的经历。

    273 引用 • 2389 回帖
  • Electron

    Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。

    15 引用 • 136 回帖
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 51 关注