TreeMap 的排序及比较器问题

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

   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 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3198 引用 • 8215 回帖 • 1 关注
  • 排序
    19 引用 • 16 回帖 • 1 关注

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • 996
    13 引用 • 200 回帖 • 2 关注
  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3455 回帖 • 160 关注
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用
  • JSON

    JSON (JavaScript Object Notation)是一种轻量级的数据交换格式。易于人类阅读和编写。同时也易于机器解析和生成。

    52 引用 • 190 回帖
  • Flutter

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

    39 引用 • 92 回帖
  • FreeMarker

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

    23 引用 • 20 回帖 • 461 关注
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    88 引用 • 139 回帖
  • Flume

    Flume 是一套分布式的、可靠的,可用于有效地收集、聚合和搬运大量日志数据的服务架构。

    9 引用 • 6 回帖 • 652 关注
  • Pipe

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

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

    133 引用 • 1124 回帖 • 115 关注
  • 代码片段

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

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

    147 引用 • 973 回帖
  • 工具

    子曰:“工欲善其事,必先利其器。”

    298 引用 • 763 回帖
  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 246 关注
  • 尊园地产

    昆明尊园房地产经纪有限公司,即:Kunming Zunyuan Property Agency Company Limited(简称“尊园地产”)于 2007 年 6 月开始筹备,2007 年 8 月 18 日正式成立,注册资本 200 万元,公司性质为股份经纪有限公司,主营业务为:代租、代售、代办产权过户、办理银行按揭、担保、抵押、评估等。

    1 引用 • 22 回帖 • 786 关注
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 35 关注
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    498 引用 • 1395 回帖 • 249 关注
  • ReactiveX

    ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的 API。它组合了观察者模式,迭代器模式和函数式编程的优秀思想。

    1 引用 • 2 回帖 • 183 关注
  • 大数据

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

    93 引用 • 113 回帖 • 2 关注
  • 倾城之链
    23 引用 • 66 回帖 • 166 关注
  • jsoup

    jsoup 是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM,CSS 以及类似于 jQuery 的操作方法来取出和操作数据。

    6 引用 • 1 回帖 • 486 关注
  • 音乐

    你听到信仰的声音了么?

    62 引用 • 512 回帖
  • 一些有用的避坑指南。

    69 引用 • 93 回帖
  • 小薇

    小薇是一个用 Java 写的 QQ 聊天机器人 Web 服务,可以用于社群互动。

    由于 Smart QQ 从 2019 年 1 月 1 日起停止服务,所以该项目也已经停止维护了!

    35 引用 • 468 回帖 • 760 关注
  • CongSec

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

    1 引用 • 1 回帖 • 29 关注
  • 生活

    生活是指人类生存过程中的各项活动的总和,范畴较广,一般指为幸福的意义而存在。生活实际上是对人生的一种诠释。生活包括人类在社会中与自己息息相关的日常活动和心理影射。

    230 引用 • 1454 回帖 • 1 关注
  • Kotlin

    Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,由 JetBrains 设计开发并开源。Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。在 Google I/O 2017 中,Google 宣布 Kotlin 成为 Android 官方开发语言。

    19 引用 • 33 回帖 • 82 关注
  • Node.js

    Node.js 是一个基于 Chrome JavaScript 运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞 I/O 模型而得以轻量和高效。

    139 引用 • 269 回帖
  • Sphinx

    Sphinx 是一个基于 SQL 的全文检索引擎,可以结合 MySQL、PostgreSQL 做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。

    1 引用 • 224 关注