【踩坑系列】使用Comparator.comparing對中文字符串排序結果不對
2025-04-25 13:18 申城異鄉人 閱讀(328) 評論(0) 收藏 舉報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("上海", "北京"));
文章持續更新,歡迎關注微信公眾號「申城異鄉人」第一時間閱讀!
浙公網安備 33010602011771號