1. <bdo id="8zfej"></bdo>
    <li id="8zfej"><meter id="8zfej"><th id="8zfej"></th></meter></li>

    南京中博教育

    全國(guó)咨詢電話:15195455103

    三分鐘了解中博教育
    當(dāng)前位置:南京中博教育 > 課程設(shè)置 > ACCP課程JAVA技術(shù)

    Java字符串之性能優(yōu)化

    來源:未知? ? ? 作者:IT教育 ? ??

    基礎(chǔ)類型轉(zhuǎn)化成String 在程序中你可能時(shí)常會(huì)需要將別的類型轉(zhuǎn)化成String,有時(shí)候可能是一些基礎(chǔ)類型的值。在拼接字符串的時(shí)候,如果你有兩個(gè)或者多個(gè)基礎(chǔ)類型的值需要放到前面,你
    基礎(chǔ)類型轉(zhuǎn)化成String
    在程序中你可能時(shí)常會(huì)需要將別的類型轉(zhuǎn)化成String,有時(shí)候可能是一些基礎(chǔ)類型的值。在拼接字符串的時(shí)候,如果你有兩個(gè)或者多個(gè)基礎(chǔ)類型的值需要放到前面,你需要顯式的將第一個(gè)值轉(zhuǎn)化成String(不然的話像System.out.println(1+'a')會(huì)輸出98,而不是"1a")。當(dāng)然了,有一組String.valueOf方法可以完成這個(gè)(或者是基礎(chǔ)類型對(duì)應(yīng)的包裝類的方法),不過如果有更好的方法能少敲點(diǎn)代碼的話,誰(shuí)還會(huì)愿意這么寫呢?
    在基礎(chǔ)類型前面拼接上一個(gè)空串(""+1)是簡(jiǎn)單的方法了。這個(gè)表達(dá)式的結(jié)果就是一個(gè)String,在這之后你就可以隨意的進(jìn)行字符串拼接操作了——編譯器會(huì)自動(dòng)將那些基礎(chǔ)類型全轉(zhuǎn)化成String的。
    不幸的是,這是糟糕的實(shí)現(xiàn)方法了。要想知道為什么,我們得先介紹下這個(gè)字符串拼接在Java里是如何處理的。如果一個(gè)字符串(不管是字面常量也好,或者是變量,方法調(diào)用的結(jié)果也好)后面跟著一個(gè)+號(hào),再后面是任何的類型表達(dá)式:
    string_exp + any_exp
    Java編譯器會(huì)把它變成:
    new StringBuilder().append( string_exp ).append( any_exp ).toString()
    如果表達(dá)式里有多個(gè)+號(hào)的話,后面相應(yīng)也會(huì)多多幾個(gè)StringBuilder.append的調(diào)用,后才是toString方法。
    StringBuilder(String)這個(gè)構(gòu)造方法會(huì)分配一塊16個(gè)字符的內(nèi)存緩沖區(qū)。因此,如果后面拼接的字符不超過16的話,StringBuilder不需要再重新分配內(nèi)存,不過如果超過16個(gè)字符的話StringBuilder會(huì)擴(kuò)充自己的緩沖區(qū)。后調(diào)用toString方法的時(shí)候,會(huì)拷貝StringBuilder里面的緩沖區(qū),新生成一個(gè)String對(duì)象返回。
    這意味著基礎(chǔ)類型轉(zhuǎn)化成String的時(shí)候,糟糕的情況就是你得創(chuàng)建:一個(gè)StringBuilder對(duì)象,一個(gè)char[16]數(shù)組,一個(gè)String對(duì)象,一個(gè)能把輸入值存進(jìn)去的char[]數(shù)組。使用String.valueOf的話,至少StringBuilder對(duì)象省掉了。
    有的時(shí)候或許你根本就不需要轉(zhuǎn)化基礎(chǔ)類型。比如,你正在解析一個(gè)字符串,它是用單引號(hào)分隔開的。初你可能是這么寫的:
    final int nextComma = str.indexOf("'");
    或者是這樣:
    final int nextComma = str.indexOf('\'');
    程序開發(fā)完了,需求變更了,需要支持任意的分隔符。當(dāng)然了,你的第一反應(yīng)是,得將這個(gè)分隔符存到一個(gè)String對(duì)象中,然后使用String.indexOf方法來進(jìn)行拆分。我們假設(shè)有個(gè)預(yù)先配置好的分隔符就放到m_separator字段里(譯注:能用這個(gè)變量名的,應(yīng)該不是Java開發(fā)出身的吧。。)。那么,你解析的代碼應(yīng)該會(huì)是這樣的:
    private static List<String> split( final String str )
    {
        final List<String> res = new ArrayList<String>( 10 );
        int pos, prev = 0;
        while ( ( pos = str.indexOf( m_separator, prev ) ) != -1 )
        {
            res.add( str.substring( prev, pos ) );
            prev = pos + m_separator.length(); // start from next char after separator
        }
        res.add( str.substring( prev ) );
        return res;
    }
    不過后面你發(fā)現(xiàn)這個(gè)分隔符就只有一個(gè)字符。在初始化的時(shí)候,你把String mseparator改成了char mseparator,然后把setter方法也一起改了。但你希望解析的方法不要改動(dòng)太大(代碼現(xiàn)在是好使的,我為什么要費(fèi)勁去改它呢?):
    private static List<String> split2( final String str )
    {
        final List<String> res = new ArrayList<String>( 10 );
        int pos, prev = 0;
        while ( ( pos = str.indexOf("" + m_separatorChar, prev ) ) != -1 )
        {
            res.add( str.substring( prev, pos ) );
            prev = pos + 1; // start from next char after separator
        }
        res.add( str.substring( prev ) );
        return res;
    }
    正如你所看到的,indexOf方法的調(diào)用被改動(dòng)了,不過它還是新建出了一個(gè)字符串然后傳遞進(jìn)去。當(dāng)然,這么做是錯(cuò)的,因?yàn)檫€有一個(gè)indexOf方法是接收char類型而不是String類型的。我們用它來改寫一下:
    private static List<String> split3( final String str )
    {
        final List<String> res = new ArrayList<String>( 10 );
        int pos, prev = 0;
        while ( ( pos = str.indexOf(m_separatorChar, prev ) ) != -1 )
        {
            res.add( str.substring( prev, pos ) );
            prev = pos + 1; // start from next char after separator
        }
        res.add( str.substring( prev ) );
        return res;
    }
    我們來用上面的三種實(shí)現(xiàn)來進(jìn)行測(cè)試,將"abc,def,ghi,jkl,mno,pqr,stu,vwx,yz"這個(gè)串解析1000萬(wàn)次。下面是Java 641和715的運(yùn)行時(shí)間。Java7由于它的String.substring方法線性復(fù)雜度的所以運(yùn)行時(shí)間反而增加了。關(guān)于這個(gè)你可以參考下這里的資料。
    可以看到的是,簡(jiǎn)單的一個(gè)重構(gòu),明顯的縮短了分割字符串所需要的時(shí)間(split/split2->split3)。
      split split2 split3
    Java 6 4.65 sec 10.34 sec 3.8 sec
    Java 7 6.72 sec 8.29 sec 4.37 sec
    字符串拼接
    本文當(dāng)然也不能完全不提字符串拼接另外兩種方法。第一種是String.concat,這個(gè)很少會(huì)用到。它內(nèi)部其實(shí)是分配了一個(gè)char[],長(zhǎng)度就是拼接后的字符串的長(zhǎng)度,它將字符串的數(shù)據(jù)拷貝到里面,后使用了私有的構(gòu)造方法來生成了一個(gè)新的字符串,這個(gè)構(gòu)造方法不會(huì)再對(duì)char[]進(jìn)行拷貝,因此這個(gè)方法調(diào)用只創(chuàng)建了兩個(gè)對(duì)象,一個(gè)是String本身,還有一個(gè)就是它內(nèi)部的char[]。不幸的是,除非你只拼接兩個(gè)字符串,這個(gè)方法才會(huì)比較高效一些。
    還有一種方法就是使用StringBuilder類,以及它的一系列的append方法。如果你有很多要拼接的值的話,這個(gè)方法當(dāng)然是快的了。它在Java5中被首度引入,用來替代StringBuffer。它們的主要區(qū)別就是StringBuffer是線程安全的,而StringBuilder不是。不過你會(huì)經(jīng)常并發(fā)的拼接字符串么難道?
    在測(cè)試中,我們把0到100000之間的數(shù)全部進(jìn)行了拼接,分別使用了String.concat, +操作符,還有StringBuilder,代碼如下:
    String res = "";
    for ( int i = 0; i < ITERS; ++i )
    {
        final String s = Integer.toString( i );
        res = res.concat( s ); //second option: res += s;
    }       
    //third option:       
    StringBuilder res = new StringBuilder();
    for ( int i = 0; i < ITERS; ++i )
    {
        final String s = Integer.toString( i );
        res.append( s );
    }
    String.concat + StringBuilder.append
    10.145 sec 42.677 sec 0.012 sec
    結(jié)果非常明顯——O(n)的時(shí)間復(fù)雜度明顯要比O(n2) 要強(qiáng)得多。不過在實(shí)際工作中會(huì)用到大量的+操作符——因?yàn)樗鼈儗?shí)在是非常方便。為了解決這個(gè)問題,從Java6 update 20開始,引入了一個(gè)-XX:+OtimizeStringConcat開關(guān)。在Java 702和Java 715之間的版本,它是默認(rèn)打開著的(在Java 6_41中還是默認(rèn)關(guān)閉著的),因此可能你得手動(dòng)將它打開。跟其它-XX的選項(xiàng)一樣,它的文檔也相當(dāng)?shù)牟睿?br /> Optimize String concatenation operations where possible. (Introduced in Java 6 Update 20)
    我們假設(shè)Oracle的工程師實(shí)現(xiàn)這個(gè)選項(xiàng)的時(shí)候是盡了大努力的吧。坊間傳聞,它是把一些StringBuilder拼接的邏輯替換成了類似String.concat那樣的實(shí)現(xiàn)——它先生成一個(gè)合適大小的char[]然后再把東西拷貝進(jìn)去。后生成一個(gè)String。那些嵌套的拼接操作它可能也支持(str1 +(str2+str3) +str4)。打開這個(gè)選項(xiàng)后進(jìn)行測(cè)試,結(jié)果表明,+號(hào)的性能跟String.concat的十分接近:
    String.concat + StringBuilder.append
    10.19 sec 10.722 sec 0.013 sec
    我們做另外一個(gè)測(cè)試。正如前面提到的,默認(rèn)的StringBuilder構(gòu)造器分配的是16個(gè)字符的緩沖區(qū)。當(dāng)需要添加第17個(gè)字符時(shí),這個(gè)緩沖區(qū)會(huì)被擴(kuò)充。我們把100到100000間的數(shù)字分別追加到"12345678901234”的后面。結(jié)果串的長(zhǎng)度應(yīng)該是在17到20之間,因此默認(rèn)的+操作符的實(shí)現(xiàn)會(huì)需要StringBuilder重新調(diào)整大小。作為對(duì)比,我們?cè)僮隽硪粋€(gè)測(cè)試,在這里我們直接創(chuàng)建一個(gè)StringBuilder(21)來保證它的緩沖區(qū)足夠大,而不會(huì)重新調(diào)整:
    final String s = BASE + i;
    final String s = new StringBuilder( 21 ).append( BASE ).append( i ).toString();
    沒有打開這個(gè)選項(xiàng)的話,+號(hào)的實(shí)現(xiàn)會(huì)比顯式的StringBuilder的實(shí)現(xiàn)的時(shí)間要多出一半。打開了這個(gè)選項(xiàng)后,兩邊的結(jié)果是一樣的。不過有趣的是,即使是StringBuilder的實(shí)現(xiàn)本身,打開了開關(guān)后速度居然也變快了!
    +, 開關(guān)關(guān)閉 +, 開關(guān)打開 new StringBuilder(21),開關(guān)關(guān)閉 new StringBuilder(21),開關(guān)打開
    0.958 sec 0.494 sec 0.663 sec 0.494 sec
    總結(jié)
    • 當(dāng)轉(zhuǎn)化成字符串的時(shí)候,應(yīng)當(dāng)避免使用""串進(jìn)行轉(zhuǎn)化。使用合適的String.valueOf方法或者包裝類的toString(value)方法。
    • 盡量使用StringBuilder進(jìn)行字符串拼接。檢查下老舊碼,把那些能替換掉的StringBuffer也替換成它。
    • 使用Java 6 update 20引入的-XX:+OptimizeStringConcat選項(xiàng)來提高字符串拼接的性能。在近的Java7的版本中已經(jīng)默認(rèn)打開了,不過在Java 6_41還是關(guān)閉的。
     

    分享到:
    近期文章

    搶試聽名額

    名額僅剩66名

    教育改變生活

    WE CHANGE LIVES

    主站蜘蛛池模板: 99热婷婷国产精品综合| 激情综合色五月丁香六月亚洲| 亚洲综合区小说区激情区| 伊人久久亚洲综合影院| 国产成人综合亚洲AV第一页| 亚洲欧洲日韩国产综合在线二区 | 一本久久a久久精品综合香蕉| 天天综合久久一二三区| 亚洲成AV人综合在线观看| 色偷偷91久久综合噜噜噜噜| 中文网丁香综合网| 国产成人综合日韩精品婷婷九月| 伊人性伊人情综合网| 色综合久久天天综合绕观看| 亚洲综合一区二区精品导航| 国产精品亚洲综合| 色综合久久久久久久| 伊人丁香狠狠色综合久久| 色老头综合免费视频| 久久综合AV免费观看| 99久久婷婷国产综合亚洲| 激情五月激情综合网| 国产激情电影综合在线看 | 中文字幕乱码人妻综合二区三区| 色九月亚洲综合网| 丁香婷婷激情综合俺也去| 亚洲综合久久一本伊伊区| 亚洲国产成人九九综合| 亚州欧州一本综合天堂网| 色综合久久最新中文字幕| 中文字幕亚洲综合久久2| 亚洲婷婷综合色高清在线| 亚洲欧洲综合在线| 一本久道综合色婷婷五月| 日韩无码系列综合区| 伊人婷婷综合缴情亚洲五月| 国产综合久久久久| 伊人色综合一区二区三区影院视频| 久久婷婷成人综合色| 亚洲综合激情六月婷婷在线观看 | 五月天激情综合网|