posts - 2, comments - 2, trackbacks - 0, articles - 23
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          日歷

          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          相關(guān)博客

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          轉(zhuǎn)帖-String詳解一:基礎(chǔ)

          Posted on 2012-02-27 14:49 齊納爾多 閱讀(140) 評(píng)論(0)  編輯  收藏 所屬分類: java
          轉(zhuǎn)帖地址:http://congmo.github.com/blog/2012/02/16/1-translationofstring/(蔥末)

          譯者注:翻譯這篇文章是有目的性的,不是閑來無事打發(fā)時(shí)間。剛剛看完String的源碼,雖然看完,但是有很多東西(或者說”陷阱”)在源碼中得不到體現(xiàn)。可能是在編譯器中進(jìn)行了優(yōu)化。無意中發(fā)現(xiàn)了這篇文章,里面講述了一些隱含的,源碼中比較隱晦或者看不到的東西。比如substring,intern等。所以才有了翻譯的”動(dòng)機(jī)”,還有一篇專門講述intern的,將在之二中翻譯。另,翻譯純屬個(gè)人行為,因技術(shù)與英語水準(zhǔn)有限,文中肯定不乏欠妥之處,如果你能文明的指出,在下將感激不盡。如言辭中滿是不尊重,則請(qǐng)收回。希望能帶給其大家?guī)椭赃_(dá)共同進(jìn)步之目的。

          Java中的字符串不同于與C++中的字符串,不能改動(dòng)字符串中的字符。預(yù)查找字符串中某個(gè)字符,可是使用charAt()方法。Java中的字符串都是16位的Unicode。可是使用StringBuffer或者char修改字符串。從1.5版本之后,可以使用StringBuilder替代StringBuffer,StringBuilder速度更快,但是是線程不安全的。

          String.length()用來獲取字符串的長度,而不是像其他類中使用的length或size()。

          空字符串

          Java中有3種空字符串:null,”“和” “。下面就是如何區(qū)別這3中空字符串的方法。

          1 if( s==null) echo( "was null" );
          2 else if( s.length() ==0) echo( "was empty" );
          3 else if( s.trim().length() ==0) echo( "was blank or other whitespace" );
          

          字符串比較

          1 if( "abc".equals(s) ) echo( "matched" );
          

          1 if( s.equals( "abc" ) ) echo( "matched" );
          

          當(dāng)s為null時(shí),不會(huì)拋出異常,只會(huì)當(dāng)作它們不相等。 除非使用String.intern()對(duì)字符串進(jìn)行合并(interned),否則不可以使用==來判斷兩個(gè)字符串是否相等。要使用equals()方法來比較。

          如果一不小心誤用==來比較字符串,編譯器也不會(huì)發(fā)出警告。不幸的是,這個(gè)bug直到編譯器或者虛擬機(jī)顯式進(jìn)行規(guī)范化(interning)時(shí),才會(huì)凸顯出。規(guī)范化(interning)之后,會(huì)獲得一個(gè)字符串的原始引用。這樣其他字符串的副本就可以很快被垃圾回收器回收。然而,規(guī)范化(interning)有3點(diǎn)不足:

        1. 要花費(fèi)額外的時(shí)間在一個(gè)HashTable中查找原始字符串。
        2. 在某些JVM的實(shí)現(xiàn)中,有規(guī)范化字符串最大長度為64K的限制。
        3. 在某些JVM的實(shí)現(xiàn)中,規(guī)范化后的字符串,就算不在被引用,也永遠(yuǎn)不會(huì)被垃圾回收器回收。
        4. 如果想比較兩個(gè)字符串的大小,就不能使用常規(guī)的比較操作了,可以使用compareTo()或compareToIgnoreCase()方法替代。

          1 String s ="apple";
          2 String t ="orange";
          3 if( s.compareTo(t) <0)
          4 {
          5    System.out.println( "s < t" );
          6 }
          

          compareTo的返回值:

        5. 如果s在字符表中排在t之后,返回正數(shù)。
        6. 如果s與t位置一樣,返回0.
        7. 如果s在字符表中排在t之前,返回負(fù)數(shù)。
        8. 這個(gè)時(shí)候可以粗略的把字符串當(dāng)作數(shù)字。返回值就是s-t。

          新手可能會(huì)因?yàn)橄旅娴膸讉€(gè)結(jié)果感到驚奇:

        9. "abc".compareTo( "ABC") returns "abc" > "ABC" compareTo是大小寫敏感的。
        10. "abc ".compareTo ( "abc") returns "abc " > "abc" 空格與其他字符一樣。
        11. "".compareTo( null) 會(huì)拋出:java.lang.NullPointerException 異常。
        12. ""與null不同。多數(shù)String中的方法可以很好的處理"",但是很少能接受null的。
        13. 字符串的比較是通過Unicode數(shù)字字符的比較來實(shí)現(xiàn)的。不能根據(jù)本地語言進(jìn)行調(diào)整。
        14. 當(dāng)實(shí)現(xiàn)自己的類時(shí),默認(rèn)的Object.equals不會(huì)一個(gè)個(gè)字段進(jìn)行比較。需要自行實(shí)現(xiàn)equals來比較。默認(rèn)equals只是比較兩個(gè)引用是否指向同一個(gè)對(duì)象。

          大小寫敏感與大小寫不敏感比較

           1 // String comparison, case-sensitive and insensitive.
           2 Stringapple="apple";
           3 Stringorange="orange";
           4 
           5 // case-sensitive compare for equality, faster than order comparison.
           6 booleansame=apple.equals( orange);
           7 
           8 // case-insensitive compare for equality, slower that case-sensitive comparison.
           9 booleansame=apple.equalsIgnoreCase( orange);
          10 
          11 // case-sensitive compare for order.
          12 // +ve if apple>orange, 0 if apple==orange, -ve if apple&lt;orange
          13 intorder=apple.compareTo( orange);
          14 
          15 // case-insensitive compare for order.
          16 // +ve if apple&gt;orange, 0 if apple==orange, -ve if apple&lt;orange
          17 intorder=apple.compareToIgnoreCase( orange);
          18 
          19 // If you are going compare the same strings over and over,
          20 // and you want to compare them in a case-insensitive way, it may pay
          21 // to convert them to lower case, and use the faster case-sensive compare.
          22 StringlcApple=apple.toLowerCase();
          23 StringlcOrange=orange.toLowerCase();
          24 
          25 // effectively a case-insensitive compare for equality,
          26 booleansame=lcApple.equals( lcOrange);
          27 
          28 // effectively a case-insensitive compare for order.
          29 // +ve if apple>orange, 0 if apple==orange, -ve if apple&lt;orange
          30 intorder=lcApple.compareTo( lcOrange);
          

          字符串搜索

          字符串搜索可使用indexOf和lastIndexOf。他們都可以通過fromOffset改變搜索開始的位置。返回的結(jié)果是相對(duì)于字符串開始的位置(0),而不是相對(duì)于fromOffset的位置。如果搜索時(shí)忽略大小寫,可先將字符串全部轉(zhuǎn)換成大寫或小寫。可以這樣實(shí)現(xiàn):

           1     public static voidmain( String[] args)
           2         {
           3         // use of indexOf
           4         finalStrings1="ABCDEFGABCDEFG";
           5         out.println( s1.indexOf( "CD" ) );
           6         // prints 2, 0-based offset of first CD where found.
           7 
           8         out.println( s1.indexOf( "cd" ) );
           9         // prints -1, means not found, search is case sensitive
          10 
          11         out.println( s1.toLowerCase().indexOf( "cd" ) );
          12         // prints 2,  0-based offset of first cd where found
          13 
          14         out.println( s1.indexOf( "cd".toUpperCase() ) );
          15         // prints 2,  0-based offset of first cd where found
          16 
          17         out.println( s1.indexOf( "CD",4/* start looking here, after the first CD */) );
          18         // prints 9, 0-based offset relative to the original string,
          19         // not relative to the start of the substring
          20 
          21         // use of last indexOf
          22 
          23         out.println( s1.lastIndexOf( "CD" ) );
          24         // prints 9, 0-based offset of where last CD found.
          25 
          26         out.println( s1.lastIndexOf( "cd" ) );
          27         // prints -1, means not found, search is case sensitive
          28 
          29         out.println( s1.toLowerCase().lastIndexOf( "cd" ) );
          30         // prints 9,  0-based offset of where last cd found
          31 
          32         out.println( s1.lastIndexOf( "cd".toUpperCase() ) );
          33         // prints 9,  0-based offset of where last cd found
          34 
          35         out.println( s1.lastIndexOf( "CD",8/* start looking here, prior to last */) );
          36         // prints 2, 0-based offset relative to the original string,
          37         // not relative to the start of the substring
          38 
          39         out.println( "\u00df" );
          40         // prints German esset ligature sz single ss bate-like glyph
          41 
          42         out.println( "\u00df".toUpperCase() );
          43         // prints SS, not SZ, two chars long!
          44         }
          45     }
          

          查找單個(gè)字符有很多方法,其中不乏速度比一個(gè)一個(gè)字符比較是否相等快。理想情況下,編譯器足夠智能的將indexOf方法單個(gè)字符參數(shù)轉(zhuǎn)化為char,那么可以將x.indexOf(y) >= 0 簡化為x.contains(y)。

          創(chuàng)建字符串

          字符串是不可變的,因此字符串不僅可以被無限期重用,而且還可在很多場景下共享。當(dāng)你將一個(gè)字符串變量賦給另外一個(gè)字符串變量時(shí),不會(huì)再次產(chǎn)生副本。甚至在調(diào)用substring后,賦給了新的變量,也不會(huì)創(chuàng)建新的字符串。只有在一下幾種情況下才會(huì)創(chuàng)建新的字符串:

        15. 字符串拼接
        16. 從文件中讀取字符串
        17. 愚蠢的使用new String(somethingElse)。一種情況下使用這種方式是恰當(dāng)?shù)模瑓⒁妔ubstring()
        18. 使用StringBuffer/StringBuilder的toString或substring方法
        19. toString方法

          每種對(duì)象都可以調(diào)用toString方法將自身的內(nèi)容轉(zhuǎn)化成人類可讀的形式。通常,編寫自定義類時(shí),盡管僅僅是為了degub,也要單獨(dú)實(shí)現(xiàn)toString方法。

          這樣來調(diào)用:String toShow = myThing.toString();

          默認(rèn)的Object.toString()很不智能,它不會(huì)像你期待的那樣,將類中所有字段值輸出。要達(dá)到這種預(yù)期,就要自己編碼實(shí)現(xiàn)。默認(rèn)的toString方法會(huì)比較對(duì)象的hashCode或者對(duì)象的地址。

          toString方法有個(gè)神奇的地方。在需要轉(zhuǎn)換為字符串時(shí),好像自動(dòng)調(diào)用toString進(jìn)行了轉(zhuǎn)換。

        20. 一種情況是使用:System.out.println(and brothers),其實(shí)它一點(diǎn)兒都不高深,println只是使用眾多的重載方法實(shí)現(xiàn)的。println有很多重載方法,每個(gè)基本數(shù)據(jù)類型一個(gè)。每個(gè)基本數(shù)據(jù)類型的toString方法將本身轉(zhuǎn)化為字符串。但是我們知道基本數(shù)據(jù)類型中是沒有toString方法的啊,確實(shí)是這樣,但是別忘記,有些靜態(tài)轉(zhuǎn)換方法,比如String.valueOf(double)就可以將雙精度浮點(diǎn)數(shù)轉(zhuǎn)化為字符串。對(duì)于任何String之外的對(duì)象,println方法調(diào)用的是對(duì)象自身重寫后的toString方法,再將結(jié)果傳給參數(shù)只能為String的println方法。
        21. 當(dāng)使用字符串連接符時(shí)(+),toString確實(shí)被調(diào)用了。如果將兩個(gè)對(duì)象相加,Java就假定你就是想將他們連接,于是調(diào)用各自的toString方法,將連接后的字符串返回。在字符串與基本數(shù)據(jù)類型相加的情況下,依然奏效。連接符會(huì)先把基本數(shù)據(jù)類型轉(zhuǎn)換為字符串,然后將結(jié)果連接。
        22. 字符串替換

          String.replace( char target, char replacement )、String. replace( String target, String replacement ) 兩個(gè)方法都是替換目標(biāo)字符串中出現(xiàn)的所有指定的字符或字符串,但是前者要比后者快很多。所以在替換單個(gè)字符時(shí),要使用前者,即使用單引號(hào)。不幸的是,后者只有在1.5及其之后的版本中才可以使用。

          replaceAll( String regex, String replacement ) 方法也是全部替換,區(qū)別在于replaceAll方法使用正則表達(dá)式搜索。欲使用replace( String target, String replacement ) 時(shí)千萬不能使用replaceAll(String regex, String replacement) 。第二個(gè)參數(shù)不是簡單的字符串,String. replaceAll 與Matcher. replaceAll 一樣。$代表匹配字符串的引用,\則是正則表達(dá)式中的關(guān)鍵字,所以需要將\轉(zhuǎn)義為\\\\,將$轉(zhuǎn)義為\\$。

          replaceFirst( String regex, String replacement ) 也使用正則表達(dá)式。

          Javadoc中String.replace是以 CharSequence為入?yún)⒌模瑒e擔(dān)心,String實(shí)現(xiàn)了 CharSequence接口,所以replace可以在String或StringBuilder中正常使用。

          正則表達(dá)式

          String中包含很多非常好用的正則表達(dá)式方法,比如split、matches、replaceAll還有replaceFirst。通常情況下推薦使用高效的java.util.regex中的方法,方法中的Pattern被提前編譯,并且可重用。在不考慮效率的情況下,就可以使用String中的正則表達(dá)式方法了。

          replaceAll和replace都以低效的方式實(shí)現(xiàn),每次調(diào)用都要重新編譯regex pattern。

          1 // how replace is implemented.
          2 // It uses regex techniques even though neither parameter is a regex.
          3 publicStringreplace(CharSequencetarget,CharSequencereplacement)
          4 {
          5    returnPattern.compile( target.toString(),Pattern.LITERAL)
          6    .matcher( this).replaceAll( Matcher.quoteReplacement( replacement.toString() ) );
          7 }
          

          所以,如果不止一次調(diào)用replace或replaceAll時(shí),最好使用單獨(dú)的正則表達(dá)式,編譯一次即可重用。

          substring

          substring很智能,與其他編程語言的深拷貝不同,它只是創(chuàng)建一個(gè)指向原始不可變字符串的引用。比如根據(jù)substring參數(shù)設(shè)置char的偏移值,與count屬性值后,返回一個(gè)指向它的引用,而不是全部拷貝。這樣就給調(diào)試增添了困惑,因?yàn)槊看慰吹降亩际钦麄€(gè)字符串而不是截取后的子串。這樣做有一個(gè)致命的缺點(diǎn),就是子串一直保持著整個(gè)原始字符串的引用,這樣即使原始字符串已經(jīng)沒用了,也不能被垃圾回收器回收。(事實(shí)上String對(duì)象的引用可以被回收,但是RAM中的char沒法被回收)

          所以查找字符串時(shí),使用indexOf(lookFor, offset)要好于先使用substring創(chuàng)建子串再使用indexOf(lookFor)。

          如果確切的知曉小子串會(huì)指向RAM中原始大字符串的char,使其不能被回收,這個(gè)時(shí)候可以使用littleString = new String(littleString)來創(chuàng)建一個(gè)與原始字符串無關(guān)的新字符串來避免這種情況的發(fā)生。

          如果你是通過src.zip來學(xué)習(xí)String.substring(),那么這種”陷阱”就很難被發(fā)現(xiàn)。因?yàn)樗怯眠^String的一個(gè)非公有構(gòu)造方法String (int offset, int count, char value) 來調(diào)整value的偏移值和count值來實(shí)現(xiàn)。

          原文鏈接:http://mindprod.com/jgloss/string.html
                           http://mindprod.com/jgloss/substring.html

          主站蜘蛛池模板: 四子王旗| 汉源县| 陵川县| 赣州市| 石台县| 宕昌县| 吉安市| 黄大仙区| 承德县| 阿荣旗| 龙泉市| 台前县| 临邑县| 德兴市| 公主岭市| 宿迁市| 沈阳市| 嘉荫县| 怀安县| 金堂县| 绥宁县| 泽州县| 长寿区| 瑞昌市| 佛学| 镇平县| 衡水市| 天全县| 彰武县| 修文县| 宁晋县| 河源市| 延边| 大同县| 芜湖县| 竹山县| 灵宝市| 山西省| 阿拉善盟| 乳源| 凤山市|