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

1. 踩坑经历

假设有这样一个业务场景,需要对各个城市的订单量排序,排序规则为:

先根据订单量倒序排列,再根据城市名称正序排列。

示例代码:

java 复制代码
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;
    }
}
java 复制代码
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方法的源码:

java 复制代码
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编码值:

java 复制代码
// 输出: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提供了本地化的排序规则,可以按特定语言规则排序(如中文拼音),代码如下所示:

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方法。

可以执行以下代码单独看下上海与北京的比较结果:

java 复制代码
Collator collator = Collator.getInstance(Locale.CHINA);
// 输出:1,代表上海大于北京,也就是要排在北京的后面
System.out.println(collator.compare("上海", "北京"));
相关推荐
普兰店拉马努金13 分钟前
【高中数学/古典概率】4红2黑六选二,求取出两次都是红球的概率
java·概率
智商低情商凑13 分钟前
CAS(Compare And Swap)
java·jvm·面试
yangmf204014 分钟前
使用 Logstash 迁移 MongoDB 数据到 Easysearch
java·elasticsearch·搜索引擎
Tiger_shl18 分钟前
【Python语言基础】24、并发编程
java·数据库·python
FAQEW20 分钟前
Spring boot 中的IOC容器对Bean的管理
java·spring boot·后端·bean·ioc容器
05091527 分钟前
测试基础笔记第十一天
java·数据库·笔记
IDRSolutions_CN1 小时前
如何将 PDF 中的文本提取为 JSON 格式
java·经验分享·pdf·软件工程·团队开发
摘星编程1 小时前
并发设计模式实战系列(6):读写锁
java·设计模式·并发编程
Java中文社群2 小时前
最火向量数据库Milvus安装使用一条龙!
java·人工智能·后端
Apache Flink2 小时前
京东物流基于Flink & StarRocks的湖仓建设实践
java·大数据·flink