【踩坑系列】使用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 ...
随机推荐
- [记录点滴] 一个Python中实现flatten的方法
之前如果想使用flatten,一般借助于numpy.ndarray.flatten. 但是 flatten只能适用于numpy对象,即array或者mat,普通的list列表不适用. 最近找到一个轻便 ...
- 10GSFP+系列光模块
10GSFP+双纤系列光模块包括SR.LRM.LR.ER.ZR模块,它们的接口类型都是LC双工,且符合IEEE802.3ae.SFF-8472和SFF-8431标准,以下是这几种光模块的具体详情. 1 ...
- Flink监控看板Dashboard解析
一. 二.常见问题排查 1.数据反压 背压(Backpressure)机制排查 点击JobName 点击某个算子 点击Backpressure查看,状态为HIGH时,则存在数据反压问题 注:若流程为A ...
- List<Map<String, Object>> 排序
一.代码 public class Test { public static void main(String[] args) { Map<String, Object> map = ne ...
- nacos(三): 创建第一个生产者producer(单体)
因为springcloud各个版本之间适配非常神经质,所以事件明确,在本实验环节中:使用的是JDK8,选择的springboot版本是2.7.6. 可以借助阿里云的脚手架(点此进入)帮我们创建第一个s ...
- HBuilder X对vue的支持有多强?
HBuilder X对vue的支持有多强? 分类:HBuilder Vue HBuilderX中使用vue,如果是打开vue文件,会自动挂载vue语法库.如果是HTML文件里引用vue框架,需要点 ...
- Linux嵌入式设备怎么确定网络端口的速率
Linux嵌入式设备怎么确定网络端口的速率 突发奇想,就是Linux下面我能不能查询到端口的速率,以此来判断要不要频繁的发送网络数据包呢? 或者更换包利用率更高的协议呢. 于是抱着这样的想法,我开始学 ...
- [BZOJ3622] 已经没有什么好害怕的了 题解
发现难以维护差值,于是令 \(K=\frac{n+k}2\),这样就把问题转化为了"糖果"比"药片"大的组数为 \(K\) 的情况有多少种. 设 \(dp_{i ...
- 八米云-各种小主机x86系统-小白保姆式超详细刷机教程
疑难解答加微信机器人,给它发:进群,会拉你进入八米交流群 机器人微信号:bamibot 简洁版教程访问:https://bbs.8miyun.cn 准备工作 说明: 1.小节点X86 单线500M以下 ...
- AGC015D题解
简要题意 给定一个区间 \([l,r]\),从中选出若干整数按位或,求可能出现的数的方案数. 数据范围:\(1\le l\le r\le2^{60}\). 思路 首先对于 \([l,r]\) 里的数全 ...