【踩坑系列】利用Comparator.comparing对中文字符串排序结果不对 ...

打印 上一主题 下一主题

主题 1503|帖子 1503|积分 4509

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1. 踩坑经历

假设有这样一个业务场景,必要对各个城市的订单量排序,排序规则为:
先根据订单量倒序排列,再根据城市名称正序排列。
示例代码:
  1. import lombok.Getter;
  2. import lombok.Setter;
  3. import lombok.ToString;
  4. @Getter
  5. @Setter
  6. @ToString
  7. public class OrderStatisticsInfo {
  8.     private String cityName;
  9.     private Integer orderCount;
  10.     public OrderStatisticsInfo(String cityName, Integer orderCount) {
  11.         this.cityName = cityName;
  12.         this.orderCount = orderCount;
  13.     }
  14. }
复制代码
  1. public static void main(String[] args) {
  2.     List<OrderStatisticsInfo> orderStatisticsInfoList = Arrays.asList(
  3.             new OrderStatisticsInfo("上海", 1000),
  4.             new OrderStatisticsInfo("北京", 1000),
  5.             new OrderStatisticsInfo("成都", 700),
  6.             new OrderStatisticsInfo("常州", 700),
  7.             new OrderStatisticsInfo("广州", 900),
  8.             new OrderStatisticsInfo("深圳", 800)
  9.     );
  10.     orderStatisticsInfoList.sort(Comparator.comparing(OrderStatisticsInfo::getOrderCount, Comparator.reverseOrder())
  11.             .thenComparing(OrderStatisticsInfo::getCityName));
  12.     orderStatisticsInfoList.forEach(System.out::println);
  13. }
复制代码
预期结果:
北京 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方法的源码:
  1. public int compareTo(String anotherString) {
  2.     int len1 = value.length;
  3.     int len2 = anotherString.value.length;
  4.     int lim = Math.min(len1, len2);
  5.     char v1[] = value;
  6.     char v2[] = anotherString.value;
  7.     int k = 0;
  8.     while (k < lim) {
  9.         char c1 = v1[k];
  10.         char c2 = v2[k];
  11.         if (c1 != c2) {
  12.             return c1 - c2;
  13.         }
  14.         k++;
  15.     }
  16.     return len1 - len2;
  17. }
复制代码
以上海与北京的比较为例,先比较第一个字符,也就是字符上和字符北,字符上对应的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编码值:
  1. // 输出:19978
  2. System.out.println(Character.codePointAt("上海", 0));
  3. // 输出:21271
  4. System.out.println(Character.codePointAt("北京", 0));
  5. // 输出:24120
  6. System.out.println(Character.codePointAt("常州", 0));
  7. // 输出:25104
  8. System.out.println(Character.codePointAt("成都", 0));
复制代码
3. 办理方案

Java提供了本地化的排序规则,可以按特定语言规则排序(如中文拼音),代码如下所示:
  1. orderStatisticsInfoList.sort(Comparator.comparing(OrderStatisticsInfo::getOrderCount, Comparator.reverseOrder())
  2.                 .thenComparing(OrderStatisticsInfo::getCityName, Collator.getInstance(Locale.CHINA)));
  3. 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方法。
可以执行以下代码单独看下上海与北京的比较结果:
  1. Collator collator = Collator.getInstance(Locale.CHINA);
  2. // 输出:1,代表上海大于北京,也就是要排在北京的后面
  3. System.out.println(collator.compare("上海", "北京"));
复制代码
文章连续更新,欢迎关注微信公众号「申城异乡人」第一时间阅读!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

小小小幸运

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表