【踩坑系列】使用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 ...
随机推荐
- Django-Admin和第三方插件Xadmin
Admin django内置了一个强大的组件叫Admin,提供给网站管理员快速开发运营后台的管理站点. 站点文档: https://docs.djangoproject.com/zh-hans/2.2 ...
- [记录点滴]Redis实现简单消息队列
[记录点滴]Redis实现简单消息队列 目录 [记录点滴]Redis实现简单消息队列 0x00 摘要 0x01 缘由 0x02 背景概念 2.1 Redis是否适合做消息队列 2.1.1 Redis的 ...
- Idea下载插件报错
报错内容Plugin Python was not installed: Cannot download 'https://plugins.jetbrains. 解决办法 file -> se ...
- 【1】JobManager启动
一.Flink底层通信技术 Akka + Netty Akka:它是基于协程的,基于scala的偏函数 Netty:相比更加基础一点,可以为不同的应用层通信协议(RPC,FTP,HTTP等)提供支持 ...
- Markdown语法基础教学
Markdown语法基础教学 简介 Markdown是一种轻量级的标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成结构化的HTML.它的目标是实现"易读易写",并且 ...
- NET中三种主机简单理解
在NET中有三个不同的主机: .NET WebApplication 主机,也称为最小主机. 这是.NET 6中的一个新特性,旨在提供最小的启动时间和内存消耗.最小主机只包括.NET运行时的最基本组件 ...
- java list集合去重的两种方式
- 高效开发助手:深入了解Hutool工具库
一.关于Hutool 1.1 简介 Hutool是一个功能丰富且易用的Java工具库,通过诸多实用工具类的使用,旨在帮助开发者快速.便捷地完成各类开发任务. 这些封装的工具涵盖了字符串.数字.集合. ...
- 【配置化】C# dapper是怎么实现的?精短ORM
目录 一.什么是dapper 二.实现问题与思路 & 源码参考 三.小结 一.什么是dapper dapper是个组件,一个dll文件,可以通过NuGet下载. 作用:快速访问数据库并自动完成 ...
- 深入理解C++ 空类大小
在C++中,规定空类(即类中没有任何数据成员.成员函数.虚函数等成员的类)的大小为1字节,这背后主要有以下几方面的原因: 保证对象的唯一性和可区分性 在C++的面向对象编程模型中,对象是类的实例化结果 ...