13.1java
String對象是不可變的。String類中每個看起來會修改String值的方法實際上都是建立了一個全新的String對象來包含修改後的字符串內容,而最初的String對象則絲毫未動。程序員
13.2正則表達式
用於String的+與+=是Java中僅有的兩個重載過的操做符,並不容許程序員重載任何操做符。api
做者在解釋String不可變性對效率的影響時使用了javap工具,它是Java Class文件分解器,能夠反編譯。javap -c命令用來分解代碼,從輸出上看就是按函數將字節碼拆分顯示。數組
經過查看字節碼,知道了因爲String的不可變性,爲了提升運行效率例以下面的語句:安全
String s = "abc" + mango + "def" + 47;app
編譯時編譯器自動引入了java.lang.StringBuilder類,用它的append()方法完成字符串拼接,最後用它的toString()方法返回結果字符串。函數
做者而後演示了一個在循環中使用拼接的例子,若是讓編譯器自動優化,那麼在每一個循環中就會建立一個StringBuilder對象;在這時咱們應該在代碼中本身定義StringBuilder來提升效率。工具
StringBuilder是Java SE5引入的,在此以前使用StringBuffer,StringBuffer是線程安全的,所以開銷也會大一些。測試
練習1分析reusing/SprinklerSystem.java的SprinklerSystem.toString()方法(方法中使用了+重載形式),若是明確的使用StringBuilder對象是否產生過多的StringBuilder對象。
首先,使用javap -c分解原SpringklerSystem代碼,發現編譯器自動爲咱們建立了一個StringBuilder對象,而後我修改程序本身建立一個StringBuilder對象,而後將代碼中的每一行加入到StringBuilder.append()方法(每一行中也使用了+重載):
StringBuilder sb = new StringBuilder(); sb.append("valve1 = " + valve1 + " "); sb.append("valve2 = " + valve2 + " "); sb.append("valve3 = " + valve3 + " "); sb.append("valve4 = " + valve4 + " "); sb.append("i = " + i + " " + "f = " + f + " "); sb.append("source = " + source); return sb.toString();
這種狀況下使用javap -c查看,會建立了不少的StringBuilder,每一行一個。這種狀況表面上使用了StringBuilder,並且代碼看起來也很整潔,可是效率卻沒有如下形式高:
"valve1 = " + valve1 + " " + "valve2 = " + valve2 + " " + "valve3 = " + valve3 + " " + "valve4 = " + valve4 + " " + "i = " + i + " " + "f = " + f + " " + "source = " + source;
由於編譯器自動替咱們優化,僅僅使用了一個StringBuilder。
13.4
String類的大多數方法,當須要改變字符串的內容時,方法都返回一個新的String對象;若是內容沒有改變方法僅僅返回指向原字符串對象的引用。
13.5
從Java 1.5開始加入了c中printf()函數的功能,它能夠用於 PrintStream類和PrintWriter類,由於System.out對象是一個PrintStream對象,故它擁有format()方法和printf()方法(二者功能同樣,等同於c中的printf()方法用於格式化輸出)。
Java5中新添加的格式化輸出功能都由java.util.Formatter類處理。能夠將Formatter看做一個翻譯器,它將格式化字符串和數據翻譯成想要的結果。建立Formatter對象時須要向構造函數傳遞一些信息,告訴Formatter將翻譯的結果輸出到哪裏。
Formatter構造函數通過重載能夠接受多種輸出目的地,不過最經常使用的仍是PrintStream、OutputStream和FIle。
格式化說明符
常規類型、字符類型和數值類型的格式說明符語法以下:
%[argument_index$][flags][width][.precision]conversion
argument_index是一個十進制整數,用於表面參數在參數列表中的位置。第一個參數由「1$」引用,第二個參數由「2$」引用,以此類推;
flags是修改輸出格式的字符集;
width指定輸出的最小寬度,必要時經過添加空格來維持最小寬度,默認是右對齊,可經過添加-改變對齊方向。
precision,應用於String表示打印出字符的最大長度,應用於浮點數表示小數點後要顯示出的位數,超出的作舍入運算,不夠的添加0,默認是6位。
conversion是代表應該若是格式化參數的字符。
用於表示日期和時間類型的格式說明符語法以下:
%[argument_index$][flags][width]conversion
須要特殊注意的是 %b 轉換成boolean類型,對全部引用只要不會Null都會輸出true,通過編碼測試
int i = 0; f.format("%b\n", i);
輸出的依舊是true。
Java 1.5在String類中添加了靜態方法format(),它接受與Formatter.format同樣的參數,但返回一個String對象。其實在String.format()內部也使用Formatter完成相應的任務。
13.6
使用String對象的matches()方法能夠判斷該對象是否匹配方法參數表示的正則表達式;
String對象的replaceFirst()或者replaceAll()能將對象中成功匹配正則表達式的部分替代爲參數中的字符串。
正則表達式量詞的三種類型:
貪婪型:儘量多的匹配;
勉強型或非貪婪型:儘量少的匹配;例如(例子摘自http://www.jb51.net/article/31491.htm):
源字符串:aa
test1
bb
test2
cc
正則表達式一:
.*
匹配結果一:
test1
bb
test2
正則表達式二:
.*?
匹配結果二:
test1
(這裏指的是一次匹配結果,因此沒包括
test2
)
第一種正則表達式是貪婪型,第二種是勉強型。
佔有型,它是Java正則表達式獨有的,支配就是對整個字符串進行一次匹配,匹配以後返回,並不回溯。這個不大好理解,有一個例子摘自http://bbs.csdn.net/topics/390269371比較形象:
字符串爲bbb,正則表達式爲[b]*+,這是一個貪婪的匹配,直接返回bbb。可是若是是佔有型的,正則爲[b]*+b,返回結果是false,是空。
若是a*a,*是匹配優先的,也就是說先匹配,若是正則的後續部分不能再匹配,就回溯,在這個例子中,匹配字符串aaa的時候,首先a*匹配到最後一個,而後發現正則後面還有一個a無法匹配,就會將a*回溯到字符串的中間一個a,這時候正則中的最後一個a與字符串的最後一個a正好匹配,匹配結束。
若是正則是a*+a,*+是佔有優先,也就是說*+前面的字符會盡量匹配,匹配了的就不會再回溯,不會讓回去了,即所謂佔有。若是字符串是aaa,那麼這個例子中匹配過程就是a*+匹配了字符串的三個a,正則中的最後一個a不會再被匹配,由於a*+不會回溯。
接口java.lang.CharSequence從CharBuffer、String、StringBuilder和StringBuffer類中抽象出了字符序列的通常定義:
interface CharSequence { charAt(int i); length(); subSequence(int start, int end); toString(); }
多數正則表達式的操做都接受CharSequence類型的參數。
使用java.util.regex.Pattern類的靜態函數complie()方法來編譯正則表達式。它會根據String類型的正則表達式生成一個Pattern對象。接下來把想要檢索的字符串傳入Pattern對象的matcher()方法。matcher()方法會生成一個Matcher對象。matches()方法用來判斷整個輸入字符串是否匹配正則表達式模式,而lookingAt()用來判斷該字符串(沒必要是整個字符串)的始部分是否可以匹配模式。find()方法嘗試查找與該模式匹配的輸入序列的下一個子序列。group()方法匹配的輸入子序列。find(int i)的重載版本,輸入的整數參數是字符串中字符的位置,以其做爲搜索的起點。注意若是使用
while(m.find(i)) {
}
這種形式,必定記得在循環裏改變i的值或者加入break條件,不然每次m.find(i)都從固定的位置開始查找匹配,會陷入死循環。
類Pattern類提供了靜態方法
static boolean matches(String regex, CharSequence input)
該方法用以檢查regex是否匹配了整個input參數。編譯後的Pattern對象還提供了split()方法,它從匹配了regex的地方分隔輸入字符串,返回分隔後的子字符串String數組。
(?i)在Java的正則表達式中表示忽略大小寫。
組是用括號劃分的正則表達式,能夠用組號來引用組,組號爲0表示整個正則表達式,組號爲1表示第一個括號包含的正則表達式,以此類推。例如:
A(B(C))D
group0表示ABCD,group1表示BC,group2表示C。
Matcher類的對象有一系列獲取組相關信息的方法:
public int groupCount()返回該匹配器模式中的分組數目,第0組不包括在內。
public String group()返回前一次匹配模式操做(例如find())的第0組。
public String group(int i )返回前一次匹配模式操做指定組號的組,若是匹配成功,可是指定的組沒有匹配輸入字符串的任何部分,則會返回Null;
public in start(int group)返回在前一次匹配操做中尋找到的組的起始索引;
public int end(int group)返回在前一次匹配操做中尋找到的組的最後一個字符索引加一的值。
例子中的正則表達式使用了(?m),正常狀況下將$與整個輸入序列的末端相匹配,使用模式標記(?m)顯示的告訴正則表達式注意輸入序列中的換行符。
在匹配操做成功以後,start()返回先前匹配的起始位置的索引,end()返回匹配的最後字符的索引加1的值。匹配操做失敗以後(或先與一個正在進行的匹配操做去嘗試)調用start()或end()將會產生IllegalStateException。
find()能夠在輸入的任意位置定位正則表達式,而lookingAt()和matches()只有在正則表達式與輸入的最開始處就開始匹配時纔會成功。matches()只有在整個輸入都匹配正則表達式時纔會成功,而lookingAt()只要輸入的第一部分匹配就會成功。
Pattern類的compile()方法有一個重載版本,它接受一個flag參數,以調整匹配的行爲:
Pattern Pattern.compile(String regex, int flag);
Pattern.Case_INSENSITIVE、Pattern.NULTILINE以及Pattern.COMMENTS比較經常使用。另外,能夠直接在正則表達式中使用其中大多數標記,只要將上表括號括起的字符插入到正則表達式中但願起到做用的位置便可。
Pattern對象的
String[] split(CharSequence input)
String[] split(CharSequence input, int limit)
將輸入字符串根據正則表達式斷開成字符串對象數組。第二種形式的split()方法能夠限制輸入分割成字符串的數量。
Matcher類的對象有一個強大的appendReplacement(StringBuffer, String)方法,它執行如下操做:
(1)從添加位置開始在輸入序列讀取字符並將其添加到StringBuffer中,在匹配以前的那一字符中止;
(2)將給定的字符串添加到StringBuffer;
(3)將此匹配器的添加位置設置爲最後匹配位置的索引加1,即end();
在執行一次或屢次appendReplacement()以後,調用appendTail(StringBuffer)方法將輸入字符串剩餘的部分複製到sbuf中。替換字符串還能夠包含匹配的組引用,$g將被group(g)的計算結果替換。
Matcher對象的reset()方法能夠從新設定字符序列。
練習1七、1八、19讓咱們解析java源代碼文件,使用正則表達式時總有想不全的狀況,寫出正確全面的正則表達式很是有挑戰性,做者推薦了一個java代碼解析器叫javacc。
13.7
Java 1.5新增了java.util.Scanner類,它的構造器能夠接受任何類型的輸入對象,包括File對象、InputStream、String或者Readable對象。Readable是1.5新增的接口,表示具備read()方法的某種類,它的實現類主要包括**Reader。用Scanner,全部的輸入、粉刺以及翻譯的操做 都隱藏在不一樣類型的next方法中。普通的next()方法返回下一個String。全部基本類型(除char以外)都有對應的next方法,包括BigDecimal和BigInteger。全部的next方法只有在找到一個完整的分詞以後纔會返回。還有相應的hasNext方法,用來判斷下一個輸入分詞是不是所需的類型。
Scanner的操做不會拋出IOException,而是將它們吞掉,可使用ioException()方法返回最近底層產生的IOException。
默認狀況下Scanner使用空白字符對輸入進行分詞,可使用正則表達式指定本身須要的定界符:
useDelimiter(String regex);
delimiter()方法用來返回該Scanner對象的定界符使用的正則表達式的Pattern對象。
next(Pattern)和hasNext(Pattern)兩個函數的重載版本使用正則表達式匹配。須要注意的是,它僅僅針對下一個分詞進行匹配,若是正則表達式中含有定界符,匹配永遠不會成功。
13.8
做者提到StringTokenizer類,在Java引入正則表達式(1.4開始)和Scanner類後(1.5開始),StringTokenizer處於廢棄狀態,官方api推薦使用Stirng.split()或java.util.regex包中的功能替代它。