嚴防Math.abs()返回負數

最近有個線上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],如果原值是最小的值取反之後不就越界了嘛。

空口無憑,我們做個試驗:

可以看到,試驗結果與現場完全吻合。

其實這個坑我很早以前就知道了,並不是什麼稀奇的問題,但是這次還是犯錯了,所以在此記錄一下,嚴防以後再犯,也和大家分享一下,希望大家以後注意。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章