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

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

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

    Sky's blog

    我和我追逐的夢(mèng)

    常用鏈接

    統(tǒng)計(jì)

    其他鏈接

    友情鏈接

    最新評(píng)論

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

        最近在公司內(nèi)部做了一些收集和整理的工作,關(guān)于trouble shooting和performace tuning 中遇到并解決的典型問(wèn)題,做了一些內(nèi)部分享。我整理了一下,準(zhǔn)備陸續(xù)放上來(lái)分享給大家。
        這些問(wèn)題,單個(gè)看每個(gè)問(wèn)題都不算復(fù)雜或高深,但是都是在實(shí)際項(xiàng)目開(kāi)發(fā)中出現(xiàn)并一度造成困擾的,而且?guī)в幸欢ǖ钠者m性,具體表現(xiàn)為不知道這些問(wèn)題的同學(xué)很容易在日常開(kāi)發(fā)中中招。因此我們開(kāi)了一個(gè)專題,叫做編碼最佳實(shí)踐,似乎名字起的有點(diǎn)大......
        先來(lái)看看第一個(gè),如何做compare。
        先看案例,問(wèn)題的表現(xiàn)很簡(jiǎn)單,就是在排序后的結(jié)果中有時(shí)會(huì)很驚訝的發(fā)現(xiàn)排序錯(cuò)誤。我們不糾結(jié)于具體的錯(cuò)誤表現(xiàn)細(xì)節(jié)和排查的過(guò)程,直接來(lái)看最終被檢查出問(wèn)題所在的代碼,這是一個(gè)很普通的Comparator接口實(shí)現(xiàn):
    private static class keyOrderComparator implements Comparator<Persistent> {
        public int compare(Persistent p1, Persistent p2) {
            return (int) (p1.getId().getKey() - p2.getId().getKey());
        }
    }
        代碼中的比較邏輯很簡(jiǎn)單,比較Persistent對(duì)象的id的key值就OK,實(shí)現(xiàn)中將兩個(gè)key簡(jiǎn)單做一次減法運(yùn)算,將結(jié)果作為compare()方法的返回值。如果p1的key大于 p2的key,則"p1.getId().getKey() - p2.getId().getKey()"的結(jié)果大于0,而compareTo()方法返回一個(gè)大于0的整數(shù)表示比較結(jié)果為"參數(shù)p1大于參數(shù)p2"。
        但麻煩出現(xiàn)在key的數(shù)據(jù)類型上,這是一個(gè)long類型,因此減法運(yùn)算的結(jié)果也是一個(gè)long,為了滿足compare()方法要求返回int的要求,在return前做了一次強(qiáng)制類型轉(zhuǎn)換。而問(wèn)題就出現(xiàn)在這里:從long到int的強(qiáng)制類型轉(zhuǎn)換是有風(fēng)險(xiǎn)的,如果long的數(shù)字超過(guò)了int所能表示的范圍[Integer.Min_VALUE,  Integer.Max_VALUE],則會(huì)發(fā)生"數(shù)據(jù)溢出"(data overflow)。
        我們可以試著執(zhí)行以下代碼 System.out.println((int) (30000000000L - 1)); , 會(huì)發(fā)現(xiàn)它的結(jié)果是一個(gè)"-64771073",和意想中的29999999999完全不同,重要的是符號(hào)變了:從一個(gè)正數(shù)變成了負(fù)數(shù)!這直接導(dǎo)致了compare()方法得出了一個(gè)令人驚訝的比較結(jié)果:30000000000 比 1 小!
        解決方式也很簡(jiǎn)單,不要做強(qiáng)制類型轉(zhuǎn)換:
    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;
    }
        }
    }
        在這個(gè)簡(jiǎn)單案例當(dāng)中,有一個(gè)比較明顯的地方可以幫助我們發(fā)現(xiàn)問(wèn)題,就是(int)這個(gè)強(qiáng)制類型轉(zhuǎn)換,稍有經(jīng)驗(yàn)的同學(xué)就會(huì)第一時(shí)間反應(yīng)過(guò)來(lái):long到int是有數(shù)據(jù)溢出風(fēng)險(xiǎn)的。那如果我們將這個(gè)案例稍微修改一下,假設(shè)p1.getId().getKey()返回的就是普通的int,結(jié)果會(huì)如何:
    private static class keyOrderComparator implements Comparator<Persistent> {
        public int compare(Persistent p1, Persistent p2) {
            return p1.getId().getKey() - p2.getId().getKey();
        }
    }
        這段代碼貌似就沒(méi)有問(wèn)題啦?呵呵,讓我們把這段代碼的業(yè)務(wù)含義去掉,退化為一個(gè)普通的int比較:
    private static class IntegerOrderComparator implements Comparator<Integer> {
        public int compare(Integer p1, Integer p2) {
            return p1 - p2;
        }
    }
        這下應(yīng)該能看出來(lái)了吧?如果p1=2147483647即Integer.MAX_VALUE,而p2=-1,則p1 - p2 = Integer.MAX_VALUE - (-1) = -2147483648 ! IntegerOrderComparator 會(huì)給出一個(gè)令人目瞪口呆的比較結(jié)果:2147483647 比 -1 小!類似的,在 p1= -2147483648 (Integer.MIN_VALUE), p2 = 1時(shí),IntegerOrderComparator 同樣會(huì)給出類似荒唐的比較結(jié)果:-2147483648 比 1 大!
        導(dǎo)致錯(cuò)誤發(fā)生的原因依然是"數(shù)據(jù)溢出"!和前面long到int的強(qiáng)制類型轉(zhuǎn)換不同,這次數(shù)據(jù)溢出發(fā)生在int與int之間做數(shù)學(xué)運(yùn)算。
        我們來(lái)看問(wèn)題發(fā)生在哪里:"int - int"這樣的簡(jiǎn)單的運(yùn)算,在我們的數(shù)學(xué)常識(shí)中,兩個(gè)整型相減結(jié)果肯定還是整型,一個(gè)正數(shù)減一個(gè)負(fù)數(shù)結(jié)果肯定是正數(shù),一個(gè)負(fù)數(shù)減一個(gè)正數(shù)結(jié)果肯定是負(fù)數(shù)......但是這里的數(shù)學(xué)常識(shí)中所謂的"整型",其取值范圍可以是無(wú)窮小到無(wú)窮大,而java語(yǔ)言(其他語(yǔ)言也是類似)中的int,只能表示[Integer.Min_VALUE,  Integer.Max_VALUE],即[-2147483648, 2147483647]這樣一個(gè)范圍。一旦運(yùn)算的結(jié)果超過(guò)這個(gè)范圍,就會(huì)發(fā)生數(shù)據(jù)溢出。
        
        因此,在java中,類似"int + int", "int - int", "int * int" 這樣的運(yùn)算結(jié)果,用int來(lái)表示是不安全的,需要使用更大的數(shù)據(jù)類型比如long來(lái)。上面的代碼可以修訂為:
    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的寫法,遇到數(shù)據(jù)范圍更大的數(shù)據(jù)類型時(shí)依然有麻煩,因?yàn)榭偸且业揭粋€(gè)比它數(shù)據(jù)范圍還要大的數(shù)據(jù)類型來(lái)承載這個(gè)diff的結(jié)果。因此還是推薦使用前面的比較方法:不做減法,直接做等于和大于/小于的比較。
        最后總結(jié)一下這個(gè)案例:
    1. compare方法實(shí)現(xiàn)時(shí),盡量不要用"return p1 - p2"這種寫法
    2. 但凡進(jìn)行數(shù)值運(yùn)算時(shí),都要小心考慮數(shù)據(jù)溢出的風(fēng)險(xiǎn)
    3. 做trouble shooting時(shí),要留意可能的數(shù)據(jù)溢出

    PS: 有沒(méi)有犯同樣錯(cuò)誤而不自知的同學(xué)?請(qǐng)自覺(jué)的留個(gè)言,呵呵

    補(bǔ)充:關(guān)于數(shù)據(jù)溢出,還有一些更加隱蔽的情況,需要小心,比如下面這段代碼:

            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);

    輸出結(jié)果為:

    msOfDay = 86400000
    msOfWeek = 604800000
    msOf30Days = -1702967296

    發(fā)現(xiàn)msOf30Days發(fā)生數(shù)據(jù)溢出了,但是明明這里long可以容納的下計(jì)算結(jié)果的。問(wèn)題發(fā)生在”1000 * 60 * 60 * 24 * 30“,這里都是int,因此操作的結(jié)果也是一個(gè)int,在賦值給long之前就已經(jīng)溢出了。解決的方法就是提前轉(zhuǎn)為long,“long msOf30Days = 1000L * 60 * 60 * 24 * 30;”,這樣就OK了。

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

    評(píng)論

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

    其實(shí)java是類型語(yǔ)言,最好的辦法是避免用減號(hào)來(lái)判斷,這是c語(yǔ)言的思維。
    最好用Integer.compare(XXX),這是絕對(duì)不會(huì)出錯(cuò)的。  回復(fù)  更多評(píng)論   

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

    類似的class其實(shí)還有很多,多注意一下,Java其實(shí)已經(jīng)內(nèi)置了很多類型,一般情況是絕對(duì)夠用了的。
    簡(jiǎn)單類型int等的自動(dòng)轉(zhuǎn)換的確是個(gè)要注意的問(wèn)題,所以在定義bean的時(shí)候,一般都要避免用int,多用Integer,Long等,封裝還是很有用的。
    我一般用int都是確定肯定為正之類的情況,要不然會(huì)多注意很多東西。
    乘除是一直要注意溢出的,這個(gè)就不用多說(shuō)了。  回復(fù)  更多評(píng)論   

    主站蜘蛛池模板: 免费成人在线观看| 亚洲福利视频一区二区三区| 国产成人亚洲综合一区| 色窝窝亚洲av网| 日韩一级免费视频| 67pao强力打造67194在线午夜亚洲 | 一级毛片免费观看不卡视频| 在线a人片天堂免费观看高清| 亚洲乱码av中文一区二区| a级毛片毛片免费观看久潮喷| 亚洲精品乱码久久久久久自慰 | 亚洲熟妇少妇任你躁在线观看| 无码人妻精品一二三区免费| 亚洲13又紧又嫩又水多| 四虎成人精品一区二区免费网站| 久久精品国产亚洲av天美18| 免费精品国产自产拍观看| 一级毛片试看60分钟免费播放| 中文字幕亚洲一区二区三区 | 亚洲精品V天堂中文字幕| 四虎永久在线精品视频免费观看| 免费夜色污私人影院网站电影| 亚洲色婷婷综合久久| 99久久精品免费精品国产| 亚洲va成无码人在线观看| 亚洲国产精品毛片av不卡在线| 国产真人无码作爱视频免费| 国产精品亚洲综合五月天| 免费A级毛片无码A| 免费在线中文日本| 亚洲中文字幕久久久一区| 亚洲伊人成无码综合网 | 一级女人18片毛片免费视频| 亚洲欧洲日韩不卡| 午夜电影免费观看| 两性色午夜视频免费网| 亚洲午夜久久久精品电影院| 久久午夜羞羞影院免费观看| 亚洲第一成年网站视频| 亚洲AV无码久久精品狠狠爱浪潮| 性色av免费观看|