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. Project Euler 728 题解

    Problem 728 Circle of Coins 得到 Wallbreaker5th 的指导. \(F\) 就是求这些环上区间(记为 \(A\))的异或线性基大小.令 \(A'_i\gets A ...

  2. JavaGUI - [01] 常见API

    题记部分 一.Component 作为基类,提供了如下常用的方法来设置组件的大小.位置.可见性等. setLocation(int x,int y) 设置组件的位置 setSize(int width ...

  3. [解决方案]Refusing to install package with name "codemirror" under a package

    前言 安装codeMirror,报错了 报错信息:Refusing to install package with name "codemirror" under a packag ...

  4. python xlrd xlwr函数 读取excel好文总结

    https://www.cnblogs.com/python-robot/p/9958352.html 官网: http://www.python-excel.org/ 好文: https://www ...

  5. Qt 设置QTableView表格列宽自动均分表格

    文章目录 Qt 设置QTableView表格列宽自动均分表格 前言 setSectionResizeMode 通过获取字体占的像素来设置 Qt 设置QTableView表格列宽自动均分表格 前言 最近 ...

  6. 『Plotly实战指南』--绘图初体验

    今天,打算通过绘制一个简单的散点图,来开启我们 Plotly 绘图的初次尝试. 本文目的不是介绍如何绘制散点图,而是通过散点图来介绍Plotly 绘图的基础步骤. 1. 绘制散点图:初探 Plotly ...

  7. idea 登录提示Server's certificate is not trusted

    原因:你本地的idea证书不可以 解决方式1: 你去安装一个正版的: 解决方式2: 设置接受不受信任证书即可. AS:File - Settings - Tools - Server Certific ...

  8. C#实现自己的Json解析器(LALR(1)+miniDFA)

    C#实现自己的Json解析器(LALR(1)+miniDFA) Json是一个用处广泛.文法简单的数据格式.本文介绍如何用bitParser(拥有自己的解析器(C#实现LALR(1)语法解析器和min ...

  9. Docker图形化工具Portainer安装

    Docker方式安装 我们可以直接使用 **Docker** 的方式来安装 首先创建数据卷,实现数据持久化 docker volume create portainer_db 启动 **Partain ...

  10. VMware网络虚拟化介绍(之一)

    2014年5月,在我加入VMware三个月之后,我涂鸦了一篇<扒一扒SDN的那些事儿>,当时放言如果阅读量过百就写续篇.后来果然阅读量没过百,也就80多的样子,其中好几份还是我自恋地进去查 ...