譯者注:翻譯這篇文章是有目的性的,不是閑來無事打發時間。剛剛看完String的源碼,雖然看完,但是有很多東西(或者說”陷阱”)在源碼中得不到體現。可能是在編譯器中進行了優化。無意中發現了這篇文章,里面講述了一些隱含的,源碼中比較隱晦或者看不到的東西。比如substring,intern等。所以才有了翻譯的”動機”,還有一篇專門講述intern的,將在之二中翻譯。另,翻譯純屬個人行為,因技術與英語水準有限,文中肯定不乏欠妥之處,如果你能文明的指出,在下將感激不盡。如言辭中滿是不尊重,則請收回。希望能帶給其大家幫助,以達共同進步之目的。
Java中的字符串不同于與C++中的字符串,不能改動字符串中的字符。預查找字符串中某個字符,可是使用charAt()方法。Java中的字符串都是16位的Unicode。可是使用StringBuffer或者char修改字符串。從1.5版本之后,可以使用StringBuilder替代StringBuffer,StringBuilder速度更快,但是是線程不安全的。
String.length()用來獲取字符串的長度,而不是像其他類中使用的length或size()。
空字符串
Java中有3種空字符串:null,”“和” “。下面就是如何區別這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" );
當s為null時,不會拋出異常,只會當作它們不相等。 除非使用String.intern()對字符串進行合并(interned),否則不可以使用==來判斷兩個字符串是否相等。要使用equals()方法來比較。
如果一不小心誤用==來比較字符串,編譯器也不會發出警告。不幸的是,這個bug直到編譯器或者虛擬機顯式進行規范化(interning)時,才會凸顯出。規范化(interning)之后,會獲得一個字符串的原始引用。這樣其他字符串的副本就可以很快被垃圾回收器回收。然而,規范化(interning)有3點不足:
要花費額外的時間在一個HashTable中查找原始字符串。在某些JVM的實現中,有規范化字符串最大長度為64K的限制。在某些JVM的實現中,規范化后的字符串,就算不在被引用,也永遠不會被垃圾回收器回收。
如果想比較兩個字符串的大小,就不能使用常規的比較操作了,可以使用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的返回值:
如果s在字符表中排在t之后,返回正數。如果s與t位置一樣,返回0.如果s在字符表中排在t之前,返回負數。
這個時候可以粗略的把字符串當作數字。返回值就是s-t。
新手可能會因為下面的幾個結果感到驚奇:
"abc".compareTo( "ABC") returns "abc" > "ABC" compareTo是大小寫敏感的。"abc ".compareTo ( "abc") returns "abc " > "abc" 空格與其他字符一樣。"".compareTo( null) 會拋出:java.lang.NullPointerException 異常。""與null不同。多數String中的方法可以很好的處理"",但是很少能接受null的。字符串的比較是通過Unicode數字字符的比較來實現的。不能根據本地語言進行調整。
當實現自己的類時,默認的Object.equals不會一個個字段進行比較。需要自行實現equals來比較。默認equals只是比較兩個引用是否指向同一個對象。
大小寫敏感與大小寫不敏感比較
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<orange
13 intorder=apple.compareTo( orange);
14
15 // case-insensitive compare for order.
16 // +ve if apple>orange, 0 if apple==orange, -ve if apple<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<orange
30 intorder=lcApple.compareTo( lcOrange);
字符串搜索
字符串搜索可使用indexOf和lastIndexOf。他們都可以通過fromOffset改變搜索開始的位置。返回的結果是相對于字符串開始的位置(0),而不是相對于fromOffset的位置。如果搜索時忽略大小寫,可先將字符串全部轉換成大寫或小寫。可以這樣實現:
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 }
查找單個字符有很多方法,其中不乏速度比一個一個字符比較是否相等快。理想情況下,編譯器足夠智能的將indexOf方法單個字符參數轉化為char,那么可以將x.indexOf(y) >= 0 簡化為x.contains(y)。
創建字符串
字符串是不可變的,因此字符串不僅可以被無限期重用,而且還可在很多場景下共享。當你將一個字符串變量賦給另外一個字符串變量時,不會再次產生副本。甚至在調用substring后,賦給了新的變量,也不會創建新的字符串。只有在一下幾種情況下才會創建新的字符串:
字符串拼接從文件中讀取字符串愚蠢的使用new String(somethingElse)。一種情況下使用這種方式是恰當的,參見substring()使用StringBuffer/StringBuilder的toString或substring方法
toString方法
每種對象都可以調用toString方法將自身的內容轉化成人類可讀的形式。通常,編寫自定義類時,盡管僅僅是為了degub,也要單獨實現toString方法。
這樣來調用:String toShow = myThing.toString();
默認的Object.toString()很不智能,它不會像你期待的那樣,將類中所有字段值輸出。要達到這種預期,就要自己編碼實現。默認的toString方法會比較對象的hashCode或者對象的地址。
toString方法有個神奇的地方。在需要轉換為字符串時,好像自動調用toString進行了轉換。
一種情況是使用:System.out.println(and brothers),其實它一點兒都不高深,println只是使用眾多的重載方法實現的。println有很多重載方法,每個基本數據類型一個。每個基本數據類型的toString方法將本身轉化為字符串。但是我們知道基本數據類型中是沒有toString方法的啊,確實是這樣,但是別忘記,有些靜態轉換方法,比如String.valueOf(double)就可以將雙精度浮點數轉化為字符串。對于任何String之外的對象,println方法調用的是對象自身重寫后的toString方法,再將結果傳給參數只能為String的println方法。當使用字符串連接符時(+),toString確實被調用了。如果將兩個對象相加,Java就假定你就是想將他們連接,于是調用各自的toString方法,將連接后的字符串返回。在字符串與基本數據類型相加的情況下,依然奏效。連接符會先把基本數據類型轉換為字符串,然后將結果連接。
字符串替換
String.replace( char target, char replacement )、String. replace( String target, String replacement ) 兩個方法都是替換目標字符串中出現的所有指定的字符或字符串,但是前者要比后者快很多。所以在替換單個字符時,要使用前者,即使用單引號。不幸的是,后者只有在1.5及其之后的版本中才可以使用。
replaceAll( String regex, String replacement ) 方法也是全部替換,區別在于replaceAll方法使用正則表達式搜索。欲使用replace( String target, String replacement ) 時千萬不能使用replaceAll(String regex, String replacement) 。第二個參數不是簡單的字符串,String. replaceAll 與Matcher. replaceAll 一樣。$代表匹配字符串的引用,\則是正則表達式中的關鍵字,所以需要將\轉義為\\\\,將$轉義為\\$。
replaceFirst( String regex, String replacement ) 也使用正則表達式。
Javadoc中String.replace是以 CharSequence為入參的,別擔心,String實現了 CharSequence接口,所以replace可以在String或StringBuilder中正常使用。
正則表達式
String中包含很多非常好用的正則表達式方法,比如split、matches、replaceAll還有replaceFirst。通常情況下推薦使用高效的java.util.regex中的方法,方法中的Pattern被提前編譯,并且可重用。在不考慮效率的情況下,就可以使用String中的正則表達式方法了。
replaceAll和replace都以低效的方式實現,每次調用都要重新編譯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 }
所以,如果不止一次調用replace或replaceAll時,最好使用單獨的正則表達式,編譯一次即可重用。
substring
substring很智能,與其他編程語言的深拷貝不同,它只是創建一個指向原始不可變字符串的引用。比如根據substring參數設置char的偏移值,與count屬性值后,返回一個指向它的引用,而不是全部拷貝。這樣就給調試增添了困惑,因為每次看到的都是整個字符串而不是截取后的子串。這樣做有一個致命的缺點,就是子串一直保持著整個原始字符串的引用,這樣即使原始字符串已經沒用了,也不能被垃圾回收器回收。(事實上String對象的引用可以被回收,但是RAM中的char沒法被回收)
所以查找字符串時,使用indexOf(lookFor, offset)要好于先使用substring創建子串再使用indexOf(lookFor)。
如果確切的知曉小子串會指向RAM中原始大字符串的char,使其不能被回收,這個時候可以使用littleString = new String(littleString)來創建一個與原始字符串無關的新字符串來避免這種情況的發生。
如果你是通過src.zip來學習String.substring(),那么這種”陷阱”就很難被發現。因為它是用過String的一個非公有構造方法String (int offset, int count, char value) 來調整value的偏移值和count值來實現。
原文鏈接:http://mindprod.com/jgloss/string.html
http://mindprod.com/jgloss/substring.html