<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Sky's blog

    我和我追逐的夢

    常用鏈接

    統計

    其他鏈接

    友情鏈接

    最新評論

    編碼最佳實踐(1)--小心"數據溢出"

        最近在公司內部做了一些收集和整理的工作,關于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: 有沒有犯同樣錯誤而不自知的同學?請自覺的留個言,呵呵

    補充:關于數據溢出,還有一些更加隱蔽的情況,需要小心,比如下面這段代碼:

            long msOfDay = 1000 * 60 * 60 * 24;
            long msOfWeek = 1000 * 60 * 60 * 24 * 7;
            long msOf30Days = 1000 * 60 * 60 * 24 * 30;
            System.out.println("msOfDay = " + msOfDay);
            System.out.println("msOfWeek = " + msOfWeek);
            System.out.println("msOf30Days = " + msOf30Days);

    輸出結果為:

    msOfDay = 86400000
    msOfWeek = 604800000
    msOf30Days = -1702967296

    發現msOf30Days發生數據溢出了,但是明明這里long可以容納的下計算結果的。問題發生在”1000 * 60 * 60 * 24 * 30“,這里都是int,因此操作的結果也是一個int,在賦值給long之前就已經溢出了。解決的方法就是提前轉為long,“long msOf30Days = 1000L * 60 * 60 * 24 * 30;”,這樣就OK了。

    posted on 2012-06-09 23:27 sky ao 閱讀(3111) 評論(2)  編輯  收藏 所屬分類: java

    評論

    # re: 代碼最佳實踐(1)--如何做compare[未登錄] 2012-06-09 23:52 stevenfrog

    其實java是類型語言,最好的辦法是避免用減號來判斷,這是c語言的思維。
    最好用Integer.compare(XXX),這是絕對不會出錯的。  回復  更多評論   

    # re: 代碼最佳實踐(1)--如何做compare[未登錄] 2012-06-09 23:57 stevenfrog

    類似的class其實還有很多,多注意一下,Java其實已經內置了很多類型,一般情況是絕對夠用了的。
    簡單類型int等的自動轉換的確是個要注意的問題,所以在定義bean的時候,一般都要避免用int,多用Integer,Long等,封裝還是很有用的。
    我一般用int都是確定肯定為正之類的情況,要不然會多注意很多東西。
    乘除是一直要注意溢出的,這個就不用多說了。  回復  更多評論   

    主站蜘蛛池模板: 成人黄软件网18免费下载成人黄18免费视频 | 国产亚洲精品va在线| 男人j进女人p免费视频| 麻豆国产VA免费精品高清在线 | 精品国产免费一区二区| 亚洲AV成人一区二区三区观看 | 亚洲国产精品免费视频| 久久久精品免费视频| 亚洲国产美国国产综合一区二区| 国产一级a毛一级a看免费人娇| 亚洲精品乱码久久久久久按摩| 色播在线永久免费视频网站| 亚洲AV永久无码区成人网站| 日本视频在线观看永久免费| 香蕉视频在线观看亚洲| 亚洲精品视频在线观看免费| 中文文字幕文字幕亚洲色| 日韩免费视频网站| 成年网站免费入口在线观看| 综合亚洲伊人午夜网| 久9久9精品免费观看| 亚洲一区二区三区国产精品无码| 好爽又高潮了毛片免费下载 | 国产午夜免费高清久久影院| 亚洲理论片在线观看| 最近2019中文免费字幕| 一级做a爰片性色毛片免费网站| 国产亚洲色婷婷久久99精品| 在线观看免费中文视频| 亚洲狠狠婷婷综合久久| 亚洲中文字幕无码久久综合网| 99久久免费中文字幕精品| 亚洲无人区码一二三码区别图片| 又黄又爽一线毛片免费观看| 免费a级毛片无码a∨免费软件| 亚洲国产亚洲综合在线尤物| 亚洲国产高清精品线久久| 99re免费在线视频| 在线观看亚洲专区| 亚洲AV无码成人精品区在线观看| 99视频在线精品免费观看6|