最近在公司内部做了一些收集和整理的工作,关于trouble shooting和performace tuning 中遇到并解决的典型问题,做了一些内部分享。我整理了一下,准备陆续放上来分享给大家。
这些问题,单个看每个问题都不算复杂或高深,但是都是在实际项目开发中出现并一度造成困扰的,而且带有一定的普适性,具体表现为不知道这些问题的同学很容易在日常开发中中招。因此我们开了一个专题,叫做编码最佳实践,似乎名字起的有点大......
先来看看第一个,如何做compare。
先看案例,问题的表现很简单,就是在排序后的结果中有时会很惊讶的发现排序错误。我们不纠结于具体的错误表现细节和排查的过程,直接来看最终被检查出问题所在的代码,这是一个很普通的Comparator接口实现:
private static class keyOrderComparator implements Comparator<Persistent> {
public int compare(Persistent p1, Persistent p2) {
return (int) (p1.getId().getKey() - p2.getId().getKey());
}
}
代码中的比较逻辑很简单,比较Persistent对象的id的key值就OK,实现中将两个key简单做一次减法运算,将结果作为compare()方法的返回值。如果p1的key大于 p2的key,则"p1.getId().getKey() - p2.getId().getKey()"的结果大于0,而compareTo()方法返回一个大于0的整数表示比较结果为"参数p1大于参数p2"。
但麻烦出现在key的数据类型上,这是一个long类型,因此减法运算的结果也是一个long,为了满足compare()方法要求返回int的要求,在return前做了一次强制类型转换。而问题就出现在这里:从long到int的强制类型转换是有风险的,如果long的数字超过了int所能表示的范围[Integer.Min_VALUE, Integer.Max_VALUE],则会发生"数据溢出"(data overflow)。
我们可以试着执行以下代码 System.out.println((int) (30000000000L - 1)); , 会发现它的结果是一个"-64771073",和意想中的29999999999完全不同,重要的是符号变了:从一个正数变成了负数!这直接导致了compare()方法得出了一个令人惊讶的比较结果:30000000000 比 1 小!
解决方式也很简单,不要做强制类型转换:
private static class keyOrderComparator implements Comparator<Persistent> {
public int compare(Persistent p1, Persistent p2) {
long key1 = p1.getId().getKey();
long key2 = p2.getId().getKey();
if (key1 == key2) {
return 0;
} else {
return key1 > key2 ? 1 : -1;
}
}
}
在这个简单案例当中,有一个比较明显的地方可以帮助我们发现问题,就是(int)这个强制类型转换,稍有经验的同学就会第一时间反应过来:long到int是有数据溢出风险的。那如果我们将这个案例稍微修改一下,假设p1.getId().getKey()返回的就是普通的int,结果会如何:
private static class keyOrderComparator implements Comparator<Persistent> {
public int compare(Persistent p1, Persistent p2) {
return p1.getId().getKey() - p2.getId().getKey();
}
}
这段代码貌似就没有问题啦?呵呵,让我们把这段代码的业务含义去掉,退化为一个普通的int比较:
private static class IntegerOrderComparator implements Comparator<Integer> {
public int compare(Integer p1, Integer p2) {
return p1 - p2;
}
}
这下应该能看出来了吧?如果p1=2147483647即Integer.MAX_VALUE,而p2=-1,则p1 - p2 = Integer.MAX_VALUE - (-1) = -2147483648 ! IntegerOrderComparator 会给出一个令人目瞪口呆的比较结果:2147483647 比 -1 小!类似的,在 p1= -2147483648 (Integer.MIN_VALUE), p2 = 1时,IntegerOrderComparator 同样会给出类似荒唐的比较结果:-2147483648 比 1 大!
导致错误发生的原因依然是"数据溢出"!和前面long到int的强制类型转换不同,这次数据溢出发生在int与int之间做数学运算。
我们来看问题发生在哪里:"int - int"这样的简单的运算,在我们的数学常识中,两个整型相减结果肯定还是整型,一个正数减一个负数结果肯定是正数,一个负数减一个正数结果肯定是负数......但是这里的数学常识中所谓的"整型",其取值范围可以是无穷小到无穷大,而java语言(其他语言也是类似)中的int,只能表示[Integer.Min_VALUE, Integer.Max_VALUE],即[-2147483648, 2147483647]这样一个范围。一旦运算的结果超过这个范围,就会发生数据溢出。
因此,在java中,类似"int + int", "int - int", "int * int" 这样的运算结果,用int来表示是不安全的,需要使用更大的数据类型比如long来。上面的代码可以修订为:
private static class IntegerOrderComparator implements Comparator<Integer> {
public int compare(Integer p1, Integer p2) {
long diff = p1 - p2;
return diff == 0 ? 0 : (diff > 0 : 1 : -1);
}
}
但是这种compare的写法,遇到数据范围更大的数据类型时依然有麻烦,因为总是要找到一个比它数据范围还要大的数据类型来承载这个diff的结果。因此还是推荐使用前面的比较方法:不做减法,直接做等于和大于/小于的比较。
最后总结一下这个案例:
1. compare方法实现时,尽量不要用"return p1 - p2"这种写法
2. 但凡进行数值运算时,都要小心考虑数据溢出的风险
3. 做trouble shooting时,要留意可能的数据溢出
PS: 有没有犯同样错误而不自知的同学?请自觉的留个爪子,呵呵
分享到:
相关推荐
对于阶乘如何向,相信学过语言的偶不陌生,一个递归函数搞定。但是当求21以上的阶乘时,我们发现数据溢出了,最多只能显示17位有效位。所以我们采用最基本的乘法运算,计算一个结果放进数组中,如此循环。
ms08067 利用工具 ms08 067溢出工具 ms08-067漏洞
CSS--文本溢出完美样式,CSS--文本溢出完美样式,CSS--文本溢出完美样式
二进制漏洞挖掘-栈溢出-开启Canary1
二进制漏洞挖掘-栈溢出-开启FORTIFY1
MS06-040溢出套装 MS06-040,溢出工具
内存溢出问题是参加kaggle比赛或者做大数据量实验的第一个拦路虎。 以前做的练手小项目导致新手产生一个惯性思维——读取训练集图片的时候把所有图读到内存中,然后分批训练。 其实这是有问题的,很容易导致OOM。...
整型数据的溢出说明。介绍了C语言中关于整型数据溢出的相关知识,适合于初学者的理解应用。
Q版缓冲区溢出教程--王炜 Q版缓冲区溢出教程--王炜 Q版缓冲区溢出教程--王炜
若从进入第一个元素开始每隔T1个时间单位进入下一个元素,同时从进入第一个元素开始,每隔T2(T2>T1)个时间单位处理完一个元素并令其出队,试编写一个算法,求出在第几个元素进队时将发生溢出。
以此为基础,提出了一种基于控制流相关数据保护的栈缓冲区溢出动态防御方法,引入了加密机制,有效地防御攻击者对保护数据的篡改。设计并实现了针对目标文件为对象的二进制文件重构工具,通过理论分析和实验表明该...
解决大批量数据导出Excel产生内存溢出的方案
java 使用 poi 解析导入大数据量(几万数据量+)时,报出OOM。这是使用POI 第二种处理方法,解决大数据量导入内存溢出问题,并提升效率
tcp-server溢出,不知道什么问题,求解释
java解决大批量数据导出Excel产生内存溢出的方案
存区域, 可以保存相同数据类型的多个实例. C程序员通常和字缓冲区数组打交道. 最常见的是字符数组. 数组, 与C语言中所有的变量一样, 可以被声明为静态或动态 的. 静态变量在程序加载时定位于数据段. 动态变量在程序...
缓冲区溢出的原理及实践,黄雁,宋茂强,缓冲区溢出漏洞是当前互联网中的重要威胁之一。本文主要阐述缓冲区溢出原理以及具体的利用过程和方法,以达到知己知彼的目的,进
1、 缓冲区距离返回地址之间的字节长度 2、 确定利用方式 1、 在这个场景中我们的 gadget 链的第一条指令是用 pop xxx 还是 ret
Mader.通用公式1除0溢出中断编码显示方法二(注解版).......................................................................................
我国股指期货之间溢出效应研究--基于VAR-BEKK-GARCH(1,1)模型,李嘉伟,伍海军,本文采用沪深300、上证50和中证500股指期货日收益率数据,构建三元VAR-BEKK-GARCH(1,1)模型,研究了三者之间的均值溢出效应和波动...