【踩坑系列】使用Comparator.comparing对中文字符串排序结果不对
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对字符串类型进行排序时,默认使用的是字符串的自然排序,即String的compareTo方法,该方法是基于
Unicode编码值进行比较的,未考虑语言特定的字符顺序(如中文拼音)。
先看下String的compareTo方法的源码:
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),在排序比较时不再执行String的compareTo方法,
而是执行Collator的compare方法,实际上是RuleBasedCollator的compare方法。
可以执行以下代码单独看下上海与北京的比较结果:
Collator collator = Collator.getInstance(Locale.CHINA);
// 输出:1,代表上海大于北京,也就是要排在北京的后面
System.out.println(collator.compare("上海", "北京"));
文章持续更新,欢迎关注微信公众号「申城异乡人」第一时间阅读!
【踩坑系列】使用Comparator.comparing对中文字符串排序结果不对的更多相关文章
- jmeter踩坑系列
1.踩坑系列一: 抓包出来有host的字段,放到jmeter里面一起请求就报错了,去掉就请求正常了 1.踩坑系列二: 从花瓶复制过去 的values 前面有空格,肉眼看起来没有
- WebGL 踩坑系列-3
WebGL 踩坑系列-3 绘制球体 在 WebGL 中绘制物体时需要的顶点是以直角坐标表示的, 当然了,gl_Position 是一个四维的向量,一般将顶点赋值给 gl_Position 时,最后一维 ...
- python踩坑系列之导入包时下划红线及报错“No module named”问题
python踩坑系列之导入包时下划红线及报错“No module named”问题 使用pycharm编写Python时,自己写了一个包(commontool),在同级另一个路径下(fileshand ...
- 踩坑系列の Oracle dbms_job简单使用
二话不说先上代码 --创建存储过程 create or replace procedure job_truncateState is begin --此处就是要定时执行的sql execute imm ...
- Vue踩坑系列
前言 前端开发对于vue的使用已经越来越多,它的优点就不做介绍了, 本篇是我对vue使用过程中遇到的问题中做的一些总结,帮助大家踩坑.如果喜欢的话可以点波赞,或者关注一下,希望本文可以帮到大家!!! ...
- 踩坑系列:MySql only_full_group_by配置,竟导致所有应用报错?
1. 踩坑经历 一个很平常的下午,大家都在埋头认真写bug呢,突然企业微信群里炸锅了,好多应用都出现大量的Error日志,而且都报同一个错误,就是下面这个: Caused by: com.mysql. ...
- 【踩坑系列】使用long类型处理金额,科学计数法导致金额转大写异常
1. 踩坑经历 上周,一个用户反馈他创建的某个销售单无法打开,但其余销售单都可以正常打开,当时查看了生产环境的ERROR日志,发现抛了这样的异常:java.lang.NumberFormatExcep ...
- electron踩坑系列之一
前言 以electron作为基础框架,已经开发两个项目了.第一个项目,我主要负责用react写页面,第二项目既负责electron部分+UI部分. 做项目,就是踩坑, 一路做项目,一路踩坑,坑多不可怕 ...
- 【踩坑系列】发送微信模板消息返回40165 invalid weapp pagepath
1. 踩坑经历 最近做了个需求,需要往公司微信公众号推送一个模板消息,并且点击该消息需要跳转到公司小程序的某个页面. 1.1 拿到模板id 既然是发送模板消息,第一步就需要登录微信公众号后台新建模板消 ...
- Jenkins踩坑系列--你试过linux主机ssh登录windows,启动java进程吗,来试试吧
一.问题概述 在一个多月前,组长让我研究下持续集成.我很自然地选择了jenkins.当时,(包括现在也是),部分服务器用的是windows主机. 我当时想了想,如果我把jenkins装在windows ...
随机推荐
- linux mint安装远程连接工具,类似于xshell的PAC
从指定的URL下载文件 wget http://sourceforge.net/projects/pacmanager/files/pac-4.0/pac-4.5.5.7-all.deb 安装依赖 ...
- 历时两天半由浅入深总结了20道Vue高频面试题
作为一个程序员如果你想要找到你心仪的工作,不可避免的就会问到很多八股文,虽然有的和工作没有半毛钱关系,但是你如果想要通过面试还必须得会.所以我最近开始总结一些面试题,一是为了加强自己的理解能够找到一份 ...
- 深度学习中CUDA环境安装教程
首先说明,本人是小白,一次安装,可能有不对的地方,望包含. 安装CUDA 因为我们是深度学习,很多时候要用到gpu进行训练,所以我们需要一种方式加快训练速度. 通俗地说,CUDA是一种协助" ...
- 用python做时间序列预测二:时间序列的一般数据格式和可视化
本文将介绍如何通过python来读取.展现时间序列数据. 读取 时间序列数据一般用cvs等电子表格的形式存储,这里以cvs为例: from dateutil.parser import parse f ...
- QT5笔记:5. QtCreator 的快捷键
常用的快捷键: F4 同名头文件和源文件之间切换 F2 声明和定义切换 Ctrl + / 注释 F10\F11 单步调试
- Powershell实现圆缩小放大 (实时刷新窗口)
使用Powershell,创建实时刷新的窗口,绘制图形,这里以圆作为例子,做缩小放大动画. [分析] Powershell是windows内置的自动部署平台,功能强大在于可以调取.net框架,因此,即 ...
- 全程不用写代码,我用AI程序员写了一个飞机大战
前言 还在为写代码薅头发吗?还在为给出的需求无处下手而发愁吗?今天宏哥分享一款开发工具的插件,让你以后的编程变得简单起来. 作为一个游戏编程小白,能完成自己工作就不错了,还能玩别的,这在以前想都不敢想 ...
- 当懒惰遇上AI:我如何用Coze让大模型帮我整理2.5万字课程笔记
能写代码绝不动手,能用AI绝不写代码 -- AI粉嫩特攻队信条 通过本文学会打造这个AI工具,只有一个要求:识字且会上网! 一个小困扰 有朋友最近在上一位大佬的线上直播课程,感叹道: "老师 ...
- Windows Api如何创建一个快捷方式并且在开始菜单搜索到自己的应用
原文链接:http://cshelloworld.com/home/detail/1804473083243925504 当我们点击win10系统搜索框的时候,输入名称 ,win10会帮助我们匹配到对 ...
- wordpress无法显示gitee图床的图片
wordpress无法显示gitee图床的图片 Question:如题 Solution:是防盗链的问题,gitee官网给出了防盗链的方法,而github貌似没有. Reference:你已经是个成熟 ...