最近在做产品需求的时候上线了一个新的产品需求,给用户多了一种新的排序排序规则,更加方便用户找到自己想要的东西。新版本发布后,QA 给我发了一个 线上崩溃 bug 链接,具体内容如下:

看到上面的链接,我有点懵逼了,就这排序还能给我搞出 bug 来?看到抛出的异常信息,也没有见过,于是直接百度搜索了。

一百度,发现很多人遇到这个问题,下面简单说下出现这个问题的原因:

在 JDK7 版本以上,Comparator 要满足自反性,传递性,对称性,不然 Arrays.sort,Collections.sort
会报 IllegalArgumentException 异常。

  • 自反性:当 两个相同的元素相比时,compare必须返回0,也就是compare(o1, o1) = 0;

  • 反对称性:如果compare(o1,o2) = 1,则compare(o2, o1)必须返回符号相反的值也就是 -1;

  • 传递性:如果 a>b, b>c, 则 a必然大于c。也就是compare(a,b)>0, compare(b,c)>0, 则compare(a,c)>0

相信很多人看到这里还是会很懵逼的,感觉自己写的代码是不会出现这个问题的,这里理解的主要难点是怎么复现这个崩溃。

任何问题在我们一开始看到的时候,都会觉得很奇怪,觉得自己写的代码是不会出现这种问题的,可是一旦复现后,就会突然顿悟了,还是有自己遗漏没有想到的 case 。

例子

demo1

其实违反上述规则最简单的例子就是如下:

new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
}
}

出现原因:没有考虑相等的情形,所以会抛出异常。

不过对于有基础的程序猿,一般都会考虑到等号的情形,所以上述代码还是很少会出现的。

new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
if (o1.getId() == o2.getId())
return 0;
return o1.getId() > o2.getId() ? 1 : -1;
}
}

如果按照上面的来,基本就不会有问题了。当然有个点需要注意的是需要判空。

不过我的崩溃和上面的例子还是很不一样的,下面举一个特殊的例子。

demo 2 (线上崩溃例子)

相信大家都用过手机的通讯录,我手机通讯录的排序方式是 # AB...YZ 这种形式的。也就是按照用户名来进行排序的,非字母类型的需要排在前面。

我出代码的问题其实就出现在对于 # 这一类名字的处理。下面看错误代码:

    private static Comparator<CompareObject> mComparatorByPlayingAndLetter = new Comparator<CompareObject>() {
@Override
public int compare(CompareObject o1, CompareObject o2) {
char firstChar = o1.name.charAt(0);
char secondChar = o2.name.charAt(0);
if (!isUpperLetters(firstChar) && (isUpperLetters(secondChar))) {
return 1;
}
if (isUpperLetters(firstChar) && !isUpperLetters(secondChar)) {
return -1;
}
if (!isUpperLetters(firstChar) && (!isUpperLetters(secondChar))) {
return 1;
}
return o1.name.compareToIgnoreCase(o2.name);
}
};

这里我先说下自己排序的算法思想:

  • 如果一个是大写字母,一个是非大写字母,那么很好排序;

  • 如果两个都是非大写字母,我返回1或者-1都可以,这里我直接给了1,对于非大写字母后面和大写字母的比较,前面的逻辑会进行处理,剩下的就是大写字母之间的比较了。

本地测试,没问题的。QA 测试也是没问题。然后这段代码上线了。

结果昨天刚发布正式版,今天就收到 QA 抛过来的线上崩溃,不过还好只是一个崩溃量。但是为啥会崩溃,我还是没法理解,我本地测试了很多遍,也还是无法复现。也百度看了很多文章,虽然知道崩溃的理论原因,但是如果无法复现,我就还是不能理解。

并且虽然我有崩溃用户的 cuid,但是崩溃的用户的数据排序我是没法拿到的,也就是还是无法复现。后来自己在已有的数据中,加了一些特殊的字符后,终于复现了。

下面来看一下 ASCII 字符表:

可以看到的是 AB...YZ 是处于后半部分的,数字和大部分特殊符号都是在大写字母前面,然后有部分标点符号是在大写字母后面的。

于是,我利用原有的数据,然后再在其中加入大写字母前后的特殊字符。对于这些数据,除了我这次新增的排序,还有其他排序,比如字母排序,创建时间排序等,不断对这些数据采用其他排序进行展示,然后再切到出问题的排序,多次来回切换排序算法,最终复现了该问题。

但是具体是哪些数据排序后引起的不满足规则,由于数据量比较大,我无法确定出来。但是可以知道的是,最后引起崩溃的两个名字只是雪花,真正有问题的地方在出现问题前就已经埋下了。

那对于上面的问题,如何解决呢?

    private static Comparator<CompareObject> mComparatorByPlayingAndLetter = new Comparator<CompareObject>() {
@Override
public int compare(CompareObject o1, CompareObject o2) {
char firstChar = o1.name.charAt(0);
char secondChar = o2.name.charAt(0);
if (!isUpperLetters(firstChar) && (isUpperLetters(secondChar))) {
return 1;
}
if (isUpperLetters(firstChar) && !isUpperLetters(secondChar)) {
return -1;
}
if (!isUpperLetters(firstChar) && (!isUpperLetters(secondChar))) {
return 1;
} // 删除红色代码即可
return o1.name.compareToIgnoreCase(o2.name);
}
};

总之,以后再写排序比较的时候,对于无法确定大小的情况,交给系统的排序,不要自己去随意改变比较值,这样就不会出现这种 case 了。

记录线上APP一个排序比较引发的崩溃 Comparison method violates its general contract!的更多相关文章

  1. 排序遇到问题 JDK7的Comparison method violates its general contract

    图解JDK7的Comparison method violates its general contract异常 楼主分析的很详细,能力有限,我看得迷迷糊糊的,不过大致知道这个错误的起因了.学习了,谢 ...

  2. 关于jdk7中 使用Collections的排序方法时报Comparison method violates its general contract!异常

    参考: Comparison method violates its general contract Comparison method violates its general contract! ...

  3. 记录线上与本地docker镜像一致,但Dockerfile却构建失败的问题

    背景 公司新开了某个项目,我在新的服务器部署了docker环境,本着ctrl+c 和ctrl+v的惯例,直接把以前的php环境的Dockerfile文件直接复制到新项目服务器那里,结果构建失败,失败的 ...

  4. Jedis线上的一个小坑:Redis有并发访问的数据错乱的问题

    问题现象: 业务数据有错乱,A的一些数据有好几个都是B的数据 这些业务数据在保存在Redis缓存中,怀疑是并发情况下Jedis错乱的问题 原因分析: JedisUtil里面在使用完Jedis 后释放资 ...

  5. 记录线上一次线程hang住问题

    线上发现执行某特定任务在某个特定时间点后不再work.该任务由线程池中线程执行定时周期性调度,根据日志查看无任何异常.从代码研判应该无关定时任务框架,因为对提交的定时任务做了wrap,会将异常都cat ...

  6. 火热的线上APP的源码分享,开箱即用

    这篇文章是写给iOS的程序员或产品经理的,同样,对于入门学习iOS开发的人,也是一个很好的实战演练,因为这里分享的是一个已经上架的.拿了源码就能正常运行起来的项目. 在介绍这个项目的源码分享之前,小编 ...

  7. 【Redis连接超时】记录线上RedisConnectionFailureException异常排查过程

    项目架构: 部分组件如下: SpringCloudAlibaba(Nacos+Gateway+OpenFeign)+SpringBoot2.x+Redis 问题背景: 最近由于用户量增大,在高峰时期, ...

  8. 如何有效的跟踪线上 MySQL 实例表和权限的变更

    介绍 从系统管理员或 DBA 的角度来讲, 总期望将线上的各种变更限制在一个可控的范围内, 减少一些不确定的因素. 这样做有几点好处: . 记录线上的库表变更; . 对线上的库表变更有全局的了解; . ...

  9. TFS线上生成环境发布历程

    继前文 TFS在项目中Devops落地进程(上) TFS在项目中DevOps落地进程(下) 自从之前将开发环境使用TFS进行了自动化之后,就享受在此成果中,其他后续进度就停顿了好一段时间. 毕竟在我们 ...

随机推荐

  1. P3348-[ZJOI2016]大森林【LCT】

    正题 题目链接:https://www.luogu.com.cn/problem/P3348 题目大意 有\(n\)棵树开始只有一个编号为\(1\)的节点且为标记点.\(m\)次操作要求支持 在\(l ...

  2. Remote Sensing Images Semantic Segmentation with General Remote Sensing Vision Model via a Self-Supervised Contrastive Learning Method

    论文阅读: Remote Sensing Images Semantic Segmentation with General Remote Sensing Vision Model via a Sel ...

  3. DistSQL:像数据库一样使用 Apache ShardingSphere

    Apache ShardingSphere 5.0.0-beta 深度解析的第一篇文章和大家一起重温了 ShardingSphere 的内核原理,并详细阐述了此版本在内核层面,特别是 SQL 能力方面 ...

  4. django3上线部署踩的坑

    好久没有用过django写项目了,最近公司开发个官网,一时兴起就拿来练练手,这不用不知道,一用吓一跳啊. 才多久,版本都到3.0了. 踩坑一:运行项目时失败报错,后来查找资料发现, 当你使用djang ...

  5. Flink Sql 之 Calcite Volcano优化器(源码解析)

    Calcite作为大数据领域最常用的SQL解析引擎,支持Flink , hive,  kylin , druid等大型项目的sql解析 同时想要深入研究Flink sql源码的话calcite也是必备 ...

  6. IT行业供应过剩?“减负路线”助你成为人人都想要的抢手开发

    开发者的IT技能:良莠不齐,优秀的软件开发人员在招聘时往往可遇不可求.包括国家统计局在内的多家权威机构的报告提示,在数字化转型的浪潮下,市场对于软件开发人员的需求数量已经远远地超过现有开发者群体的数量 ...

  7. 项目优化之v-if

    前言: 在vue项目中,由于功能比较多,需要各种条件控制某个功能.某个标签.表格中的某一行是否显示等,需要使用大量的v-if来判断条件. 例如: <div v-if="isShow(a ...

  8. Beta阶段第九次会议

    Beta阶段第九次会议 时间:2020.5.25 完成工作 姓名 完成工作 任务难度 完成度 ltx 1.发现小程序身份认证bug和新闻列表获取bug2.修改新增页面风格 轻 90% xyq 1.修改 ...

  9. BUAA-软件工程-个人总结与心得

    提问回顾以及个人总结 项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 提问回顾与个人总结 我在这个课程的目标是 学习软件开发的过程,团队之间的写作 ...

  10. 2021.9.7考试总结[NOIP模拟49]

    T1 Reverse $BFS$暴力$O(n^2)$ 过程中重复枚举了很多点,考虑用链表记录当前点后面可到达的第一个未更新点. 搜索时枚举翻转子串的左端点,之后便可以算出翻转后$1$的位置. $cod ...