1. 踩坑经历

假设有这样一个业务场景,需要对各个城市的订单量排序,排序规则为:

先根据订单量倒序排列,再根据城市名称正序排列。

示例代码:

import lombok.Getter;
import lombok.Setter;
import lombok.ToString; @Getter
@Setter
@ToString
public class OrderStatisticsInfo {
private String cityName;
private Integer orderCount; public OrderStatisticsInfo(String cityName, Integer orderCount) {
this.cityName = cityName;
this.orderCount = orderCount;
}
}
public static void main(String[] args) {
List<OrderStatisticsInfo> orderStatisticsInfoList = Arrays.asList(
new OrderStatisticsInfo("上海", 1000),
new OrderStatisticsInfo("北京", 1000),
new OrderStatisticsInfo("成都", 700),
new OrderStatisticsInfo("常州", 700),
new OrderStatisticsInfo("广州", 900),
new OrderStatisticsInfo("深圳", 800)
); orderStatisticsInfoList.sort(Comparator.comparing(OrderStatisticsInfo::getOrderCount, Comparator.reverseOrder())
.thenComparing(OrderStatisticsInfo::getCityName));
orderStatisticsInfoList.forEach(System.out::println);
}

预期结果:

北京 1000

上海 1000

广州 900

深圳 800

常州 700

成都 700

实际结果:

OrderStatisticsInfo(cityName=上海, orderCount=1000)

OrderStatisticsInfo(cityName=北京, orderCount=1000)

OrderStatisticsInfo(cityName=广州, orderCount=900)

OrderStatisticsInfo(cityName=深圳, orderCount=800)

OrderStatisticsInfo(cityName=常州, orderCount=700)

OrderStatisticsInfo(cityName=成都, orderCount=700)

从以上结果可以看出,根据订单量倒序排列没啥问题,但根据城市名称正序排列不符合预期:

上海竟然排到了北京的前面,但常州与成都的顺序又是对的。

2. 原因分析

Comparator.comparing对字符串类型进行排序时,默认使用的是字符串的自然排序,即StringcompareTo方法,该方法是基于

Unicode编码值进行比较的,未考虑语言特定的字符顺序(如中文拼音)。

先看下StringcompareTo方法的源码:

public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value; int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}

以上海与北京的比较为例,先比较第一个字符,也就是字符上和字符北,字符上对应的Unicode编码值是19978,因此c1 = 19978,

字符北对应的Unicode编码值是21271,因此c2 = 21271,因为c1 != c2,所以返回值为-1293,

也就是说上海小于北京(要排在北京的前面),不符合预期。

以常州与成都的比较为例,先比较第一个字符,也就是字符常和字符成,字符常对应的Unicode编码值是24120,因此c1 = 24120,

字符成对应的Unicode编码值是25104,因此c2 = 25104,因为c1 != c2,所以返回值为-984,

也就是说常州小于成都(要排在成都的前面),符合预期。

可以通过Character.codePointAt方法获取字符的Unicode编码值:

// 输出:19978
System.out.println(Character.codePointAt("上海", 0));
// 输出:21271
System.out.println(Character.codePointAt("北京", 0));
// 输出:24120
System.out.println(Character.codePointAt("常州", 0));
// 输出:25104
System.out.println(Character.codePointAt("成都", 0));

3. 解决方案

Java提供了本地化的排序规则,可以按特定语言规则排序(如中文拼音),代码如下所示:

orderStatisticsInfoList.sort(Comparator.comparing(OrderStatisticsInfo::getOrderCount, Comparator.reverseOrder())
.thenComparing(OrderStatisticsInfo::getCityName, Collator.getInstance(Locale.CHINA)));
orderStatisticsInfoList.forEach(System.out::println);

此时的输出结果为:

OrderStatisticsInfo(cityName=北京, orderCount=1000)

OrderStatisticsInfo(cityName=上海, orderCount=1000)

OrderStatisticsInfo(cityName=广州, orderCount=900)

OrderStatisticsInfo(cityName=深圳, orderCount=800)

OrderStatisticsInfo(cityName=常州, orderCount=700)

OrderStatisticsInfo(cityName=成都, orderCount=700)

可以看到,北京排到了上海的前面,符合预期。

上述代码指定了Collator.getInstance(Locale.CHINA),在排序比较时不再执行StringcompareTo方法,

而是执行Collatorcompare方法,实际上是RuleBasedCollatorcompare方法。

可以执行以下代码单独看下上海与北京的比较结果:

Collator collator = Collator.getInstance(Locale.CHINA);
// 输出:1,代表上海大于北京,也就是要排在北京的后面
System.out.println(collator.compare("上海", "北京"));

文章持续更新,欢迎关注微信公众号「申城异乡人」第一时间阅读!

【踩坑系列】使用Comparator.comparing对中文字符串排序结果不对的更多相关文章

  1. jmeter踩坑系列

    1.踩坑系列一: 抓包出来有host的字段,放到jmeter里面一起请求就报错了,去掉就请求正常了 1.踩坑系列二: 从花瓶复制过去 的values 前面有空格,肉眼看起来没有

  2. WebGL 踩坑系列-3

    WebGL 踩坑系列-3 绘制球体 在 WebGL 中绘制物体时需要的顶点是以直角坐标表示的, 当然了,gl_Position 是一个四维的向量,一般将顶点赋值给 gl_Position 时,最后一维 ...

  3. python踩坑系列之导入包时下划红线及报错“No module named”问题

    python踩坑系列之导入包时下划红线及报错“No module named”问题 使用pycharm编写Python时,自己写了一个包(commontool),在同级另一个路径下(fileshand ...

  4. 踩坑系列の Oracle dbms_job简单使用

    二话不说先上代码 --创建存储过程 create or replace procedure job_truncateState is begin --此处就是要定时执行的sql execute imm ...

  5. Vue踩坑系列

    前言 前端开发对于vue的使用已经越来越多,它的优点就不做介绍了, 本篇是我对vue使用过程中遇到的问题中做的一些总结,帮助大家踩坑.如果喜欢的话可以点波赞,或者关注一下,希望本文可以帮到大家!!! ...

  6. 踩坑系列:MySql only_full_group_by配置,竟导致所有应用报错?

    1. 踩坑经历 一个很平常的下午,大家都在埋头认真写bug呢,突然企业微信群里炸锅了,好多应用都出现大量的Error日志,而且都报同一个错误,就是下面这个: Caused by: com.mysql. ...

  7. 【踩坑系列】使用long类型处理金额,科学计数法导致金额转大写异常

    1. 踩坑经历 上周,一个用户反馈他创建的某个销售单无法打开,但其余销售单都可以正常打开,当时查看了生产环境的ERROR日志,发现抛了这样的异常:java.lang.NumberFormatExcep ...

  8. electron踩坑系列之一

    前言 以electron作为基础框架,已经开发两个项目了.第一个项目,我主要负责用react写页面,第二项目既负责electron部分+UI部分. 做项目,就是踩坑, 一路做项目,一路踩坑,坑多不可怕 ...

  9. 【踩坑系列】发送微信模板消息返回40165 invalid weapp pagepath

    1. 踩坑经历 最近做了个需求,需要往公司微信公众号推送一个模板消息,并且点击该消息需要跳转到公司小程序的某个页面. 1.1 拿到模板id 既然是发送模板消息,第一步就需要登录微信公众号后台新建模板消 ...

  10. Jenkins踩坑系列--你试过linux主机ssh登录windows,启动java进程吗,来试试吧

    一.问题概述 在一个多月前,组长让我研究下持续集成.我很自然地选择了jenkins.当时,(包括现在也是),部分服务器用的是windows主机. 我当时想了想,如果我把jenkins装在windows ...

随机推荐

  1. Django-Admin和第三方插件Xadmin

    Admin django内置了一个强大的组件叫Admin,提供给网站管理员快速开发运营后台的管理站点. 站点文档: https://docs.djangoproject.com/zh-hans/2.2 ...

  2. [记录点滴]Redis实现简单消息队列

    [记录点滴]Redis实现简单消息队列 目录 [记录点滴]Redis实现简单消息队列 0x00 摘要 0x01 缘由 0x02 背景概念 2.1 Redis是否适合做消息队列 2.1.1 Redis的 ...

  3. Idea下载插件报错

    报错内容Plugin Python was not installed: Cannot download 'https://plugins.jetbrains. 解决办法 file ->  se ...

  4. 【1】JobManager启动

    一.Flink底层通信技术 Akka + Netty Akka:它是基于协程的,基于scala的偏函数 Netty:相比更加基础一点,可以为不同的应用层通信协议(RPC,FTP,HTTP等)提供支持 ...

  5. Markdown语法基础教学

    Markdown语法基础教学 简介 Markdown是一种轻量级的标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成结构化的HTML.它的目标是实现"易读易写",并且 ...

  6. NET中三种主机简单理解

    在NET中有三个不同的主机: .NET WebApplication 主机,也称为最小主机. 这是.NET 6中的一个新特性,旨在提供最小的启动时间和内存消耗.最小主机只包括.NET运行时的最基本组件 ...

  7. java list集合去重的两种方式

  8. 高效开发助手:深入了解Hutool工具库

    一.关于Hutool 1.1 简介 Hutool​是一个功能丰富且易用的Java工具库,通过诸多实用工具类的使用,旨在帮助开发者快速.便捷地完成各类开发任务. 这些封装的工具涵盖了字符串.数字.集合. ...

  9. 【配置化】C# dapper是怎么实现的?精短ORM

    目录 一.什么是dapper 二.实现问题与思路 & 源码参考 三.小结 一.什么是dapper dapper是个组件,一个dll文件,可以通过NuGet下载. 作用:快速访问数据库并自动完成 ...

  10. 深入理解C++ 空类大小

    在C++中,规定空类(即类中没有任何数据成员.成员函数.虚函数等成员的类)的大小为1字节,这背后主要有以下几方面的原因: 保证对象的唯一性和可区分性 在C++的面向对象编程模型中,对象是类的实例化结果 ...