记录线上APP一个排序比较引发的崩溃 Comparison method violates its general contract!
最近在做产品需求的时候上线了一个新的产品需求,给用户多了一种新的排序排序规则,更加方便用户找到自己想要的东西。新版本发布后,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!的更多相关文章
- 排序遇到问题 JDK7的Comparison method violates its general contract
图解JDK7的Comparison method violates its general contract异常 楼主分析的很详细,能力有限,我看得迷迷糊糊的,不过大致知道这个错误的起因了.学习了,谢 ...
- 关于jdk7中 使用Collections的排序方法时报Comparison method violates its general contract!异常
参考: Comparison method violates its general contract Comparison method violates its general contract! ...
- 记录线上与本地docker镜像一致,但Dockerfile却构建失败的问题
背景 公司新开了某个项目,我在新的服务器部署了docker环境,本着ctrl+c 和ctrl+v的惯例,直接把以前的php环境的Dockerfile文件直接复制到新项目服务器那里,结果构建失败,失败的 ...
- Jedis线上的一个小坑:Redis有并发访问的数据错乱的问题
问题现象: 业务数据有错乱,A的一些数据有好几个都是B的数据 这些业务数据在保存在Redis缓存中,怀疑是并发情况下Jedis错乱的问题 原因分析: JedisUtil里面在使用完Jedis 后释放资 ...
- 记录线上一次线程hang住问题
线上发现执行某特定任务在某个特定时间点后不再work.该任务由线程池中线程执行定时周期性调度,根据日志查看无任何异常.从代码研判应该无关定时任务框架,因为对提交的定时任务做了wrap,会将异常都cat ...
- 火热的线上APP的源码分享,开箱即用
这篇文章是写给iOS的程序员或产品经理的,同样,对于入门学习iOS开发的人,也是一个很好的实战演练,因为这里分享的是一个已经上架的.拿了源码就能正常运行起来的项目. 在介绍这个项目的源码分享之前,小编 ...
- 【Redis连接超时】记录线上RedisConnectionFailureException异常排查过程
项目架构: 部分组件如下: SpringCloudAlibaba(Nacos+Gateway+OpenFeign)+SpringBoot2.x+Redis 问题背景: 最近由于用户量增大,在高峰时期, ...
- 如何有效的跟踪线上 MySQL 实例表和权限的变更
介绍 从系统管理员或 DBA 的角度来讲, 总期望将线上的各种变更限制在一个可控的范围内, 减少一些不确定的因素. 这样做有几点好处: . 记录线上的库表变更; . 对线上的库表变更有全局的了解; . ...
- TFS线上生成环境发布历程
继前文 TFS在项目中Devops落地进程(上) TFS在项目中DevOps落地进程(下) 自从之前将开发环境使用TFS进行了自动化之后,就享受在此成果中,其他后续进度就停顿了好一段时间. 毕竟在我们 ...
随机推荐
- Java——多线程编程学习/01
原文章:http://www.cnblogs.com/QG-whz/p/8351298.html 注:建议去看原博主的文章,单就这个知识点而论,比书本讲的透彻,如有违规,联系必删! 并发环境下进行编 ...
- 【问题记录】Java服务发起HTTPS请求报错:PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException
问题报错 今天上线了我开发的一个OAuth2单点登录客户端的实现,在测试系统验证没问题,到生产环境由于单点登录服务端HTTPS协议,报错如下: I/O error on POST request fo ...
- NOIP 模拟五 考试总结
T1string T1开的不错,看到这个题很激动,类似与HEOI2016排序,好像还要更简单一些,于是迅速冲了个桶排.因为洛谷上排序那道题是用桶排水的,所以我觉得没必要打线段树了,极端大数据20秒冲过 ...
- 基于go语言学习工厂模式
工厂模式 简单工厂模式(Simple Factory) 定义 优点 缺点 适用范围 代码实现 工厂方法模式(Factory Method) 定义 优点 缺点 适用范围 代码实现 抽象工厂模式(Abst ...
- 8.JVM内存分配机制超详细解析
一.对象的加载过程 之前研究过类的加载过程.具体详情可查看文章:https://www.cnblogs.com/ITPower/p/15356099.html 那么,当一个对象被new的时候,是如何加 ...
- 使用ffmpeg进行视频图片提取
环境:windows10-x64 ffmpeg的功能很强大,可以进行音频和视频的处理,这里记录下需要从视频文件提取图片的情况. ffmpeg官方地址:https://www.ffmpeg.org/使用 ...
- UE4蓝图AI角色制作(六)之行为树
13.行为树原理 AI最重要的环节就是行为树.我们将解释什么是行为树.为何它如此重要,以及构建行为树需要哪些元素. 借助行为树,我们可以轻松控制并显示AI的决策制定过程.行为树是一种将AI在场景中的决 ...
- vue 解决axios请求出现前端跨域问题
vue 解决axios请求出现前端跨域问题 最近在写纯前端的vue项目的时候,碰到了axios请求本机的资源的时候,出现了访问报404的问题.这就让我很难受.查询了资料原来是跨域的问题. 在正常开发中 ...
- FastAPI 学习之路(三十八)Static Files
如果使用前后台不分离的开发方式,那么模板文件中使用的静态文件,比如css/js等文件的目录需要在后台进行配置,以便模板渲染是能正确读到这些静态文件.那么我们应该如何处理呢. 首先安装依赖 pip in ...
- Java:AQS 小记-2(ReentrantLock)
Java:AQS 小记-2(ReentrantLock) 整体结构 ReentrantLock 类图 AbstractOwnableSynchronizer 类 public abstract cla ...