您的位置:首页 > 其它

严防Math.abs()返回负数

2017-05-12 13:32 148 查看
最近有个线上spark streaming程序跑着跑着就挂了,调查了一番,发现了一个平时大家都不太注意的问题。

看了日志,抛出的异常如下:

java.lang.ArrayIndexOutOfBoundsException: -2
at com.xiaomi.poppy.hbase.HBaseUtil.getHashPrefix(HBaseUtil.java:58)
at com.xiaomi.xmpush.spark.hbase.HBaseProcessor.buildRowKey(HBaseProcessor.java:220)
at com.xiaomi.xmpush.spark.hbase.HBaseProcessor.buildRequest(HBaseProcessor.java:203)
at com.xiaomi.xmpush.spark.hbase.HBaseProcessor.writeCounter(HBaseProcessor.java:126)
at com.xiaomi.xmpush.main.PushSparkStatMain$4$1.call(PushSparkStatMain.java:102)
at com.xiaomi.xmpush.main.PushSparkStatMain$4$1.call(PushSparkStatMain.java:97)
at org.apache.spark.api.java.JavaRDDLike$$anonfun$foreach$1.apply(JavaRDDLike.scala:330)
at org.apache.spark.api.java.JavaRDDLike$$anonfun$foreach$1.apply(JavaRDDLike.scala:330)
at scala.collection.Iterator$class.foreach(Iterator.scala:727)
at scala.collection.AbstractIterator.foreach(Iterator.scala:1157)
at org.apache.spark.rdd.RDD$$anonfun$foreach$1$$anonfun$apply$28.apply(RDD.scala:894)
at org.apache.spark.rdd.RDD$$anonfun$foreach$1$$anonfun$apply$28.apply(RDD.scala:894)
at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:1854)
at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:1854)
at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:66)


找到出错代码如下:

public static String getHashPrefix(String input, int length) {
int hash = Math.abs(getHashCodeForString(input));
final int radix = CHAR_MAP.length;
StringBuffer sb = new StringBuffer();
for (int index = 0; index < length; ++index) {
sb.append(CHAR_MAP[hash % radix]);
hash /= radix;
}
return sb.toString();
}


具体来说就是
sb.append(CHAR_MAP[hash % radix]);
这句中的
hash % radix
得到了一个负数(-2),导致数组越界。

好奇怪啊,这里的
hash
是一个绝对值,按理来说是非负数才对,而
radix
是一个数组的长度,已经固定为62了,
hash % radix
怎么可能为负数。

先别下结论,我们首先知道的是:模运算的值如果为负数,则两个运算数必须要有一个为负数,并且我们可以百分之百肯定radix不可能为负数,所以按照神探福尔摩斯所说的:排除一切不可能的,剩下的即使再不可能,那也是真相,那我们就先把『真相』的帽子戴在这个绝对值的头上。

去看了一下JDK中关于Math.abs的文档:

/**
* Returns the absolute value of an {@code int} value.
* If the argument is not negative, the argument is returned.
* If the argument is negative, the negation of the argument is returned.
*
* <p>Note that if the argument is equal to the value of
* {@link Integer#MIN_VALUE}, the most negative representable
* {@code int} value, the result is that same value, which is
* negative.
*
* @param   a   the argument whose absolute value is to be determined
* @return  the absolute value of the argument.
*/
public static int abs(int a) {
return (a < 0) ? -a : a;
}


果然找到了真相:如果是对Integer(Long也一样)取绝对值,如果原值是Integer.MIN_VALUE,则得到的绝对值和原值相等,是一个负数。为什么呢?因为你看看abs的实现,它很简单,如果原值是正数,直接返回,如果是负数,就简单地在前面加个负号。然而Integer的范围是[-2147483648,2147483647],如果原值是最小的值取反之后不就越界了嘛。

空口无凭,我们做个试验:



可以看到,试验结果与现场完全吻合。

其实这个坑我很早以前就知道了,并不是什么稀奇的问题,但是这次还是犯错了,所以在此记录一下,严防以后再犯,也和大家分享一下,希望大家以后注意。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: