可以證明,字串操作是計算機程式設計中最常見的行為。
不可變String
String物件是不可變的。檢視JDK文件你就會發現,String類中每一個看起來會修改String值的方法,實際上都是建立了一個全新的String物件,以包含修改後的字串內容,而最初的String物件則絲毫未變。
public class Immutable {
public static String upcase(String s){
return s.toUpperCase();
}
public static void main(String[] args) {
String q = "howdy";
System.out.println(q);
String qq = upcase(q);
System.out.println(qq);
System.out.println(q);
}
}
複製程式碼
過載 “+” 與StringBuilder
不可變性會帶來一定的效率問題。用於String的“+”與“+=”是Java中僅有的兩個過載過的操作符,而Java並不允許程過載任何操作符。
操作符 “+” 可以用來連線String:
public class Concatenation {
public static void main(String[] args) {
String mango = "mango";
String s = "abc" + mango;
System.out.println(s);
}
}
複製程式碼
想看看以上程式碼到底是如何工作的嗎,可以用JDK自帶的工具javap來反編譯以上程式碼,可以得到以下的位元組碼:
編譯器自動引入了java.lang.StringBuilder類(因為它更高效),通過呼叫append方法將字串連線起來,最後呼叫toString方法生成最終結果。
避免迴圈體內使用 "+="
public String implicit(String[] fields) {
String result = "";
for (int i = 0; i < fields.length; i++) {
result += fields[i];
}
return result;
}
複製程式碼
通過反編譯得如下位元組碼:
StringBuilder是在迴圈體內構造的,這意味著每經過迴圈一次,就會建立一個新的StringBuilder物件。
無意識的遞迴
public class InfiniteRecursion {
@Override
public String toString() {
// 列印InfiniteRecursion物件的記憶體地址
return " InfiniteRecursion adress: " + this + "\n";
}
public static void main(String[] args) {
InfiniteRecursion infiniteRecursion = new InfiniteRecursion();
System.out.println(infiniteRecursion);
}
}
複製程式碼
執行以上程式出現如下結果:
" InfiniteRecursion adress: " + this
這裡發生了自動型別轉換,由InfiniteRecursion型別轉換成String型別。因為編譯器發現String物件後面跟著一個 “+”,而後面的物件不是String,編譯器呼叫this.toString()方法進行型別轉換,因此發生了遞迴呼叫。
如果你真的想要列印出物件的記憶體地址,應該呼叫Object.toString()方法。所以不該使用this,而是應該呼叫super.toString()方法。
格式化輸出
Java SE5推出了C語言中printf()風格的格式化輸出這一功能,不需要使用過載的 “+”操作符來連線引用號內的字串或者字串常量,而是使用特殊的佔位符來表示資料將來的位置。
public static void main(String[] args) {
int x = 5;
double y = 5.332;
// the old way
System.out.println("Row 1:[" + x + " " + y + "]");
// the new way
System.out.printf("Row 1:[%d %f]\n", x, y);
// or
System.out.format("Row 1:[%d %f]\n", x, y);
}
複製程式碼
執行以上程式,首先將x的值插入到%d的位置,然後將y的值插入%f的位置。這些佔位符被稱為格式修飾符,它們不但說明了將插入什麼型別的變數,以及如何對其格式化。
Formatter類
在Java中,所有新的格式化功能都由java.util.Formatter類處理。可以將Formatter類看作一個翻譯器,它將你的格式化字串與資料翻譯成需要的結果。
Formatter formatter = new Formatter(System.out);
formatter.format("Row 1:[%d %f]\n", x, y);
複製程式碼
String.format()
String.format()是一個static方法,它接受與Formatter.format()方法一樣的引數,但返回一個String物件。
格式化說明符
在插入資料時,如果想要控制空格與對齊,你需要更精細複雜的格式修飾符。以下是其抽象的語法:
%[argument_index$][flags][width][.precision]conversion
複製程式碼
欄位 | 說明 |
---|---|
argument_index | 需要將引數列表中第幾個引數進行格式化 |
flags | 一些特殊的格式,比如‘-’代表向左對齊 |
width | 輸出的最小的寬度,適用於所有的引數型別 |
[.precision] | 引數為String,則表示列印String輸出字元的最大數量;引數為float,則表示小數點最大位數。不使用於int |
conversion | 接受的引數型別,如s代表後面接String型別的引數;d代表接int型的引數 |
正規表示式
正規表示式是一種強大而靈活的文字處理工具。使用正規表示式,我們能夠以程式設計的方式,構造複雜的文字模式,並對輸入的字串進行搜尋。一旦找到了匹配這些模式的部分,你就能夠隨心所欲地對它們進行處理。
Java語言與其他語言相比對反斜槓
\
有不同的處理:在其他語言中,
\\
表示“我想要在正規表示式中插入一個普通的(字面上的)反斜槓,請不要給它做任何特殊的意義。” 而在Java中,\\
的意思是“我要插入一個正規表示式的反斜槓,所以其後的字元具有特殊的意義。”例如,如果你想表示一位數字,那麼正規表示式應該是
\\d
。如果你想插入一個普通的反斜槓,則應該這樣\\\\
。不過換行和製表符之類的東西只需使用單斜槓線:\n\t
。
String類支援正規表示式的方法
public String[] split(String regex)
public String[] split(String regex, int limit)
public String replaceFirst(String regex, String replacement)
public String replaceAll(String regex, String replacement)
public boolean matches(String regex)
複製程式碼
Pattern和Matcher
一般來說,比起功能有限的String類,我們更願意構造功能強大的正規表示式物件。通過Pattern.complie()
方法來編譯你的正規表示式即可。它會根據你的String型別的正規表示式生成一個Pattern物件。接下來,把你想要檢索的字串傳入Pattern物件的matcher()方法,matcher()方法會生成一個Matcher物件,它有很多功能可用。
public class RegexExpression {
public static void main(String[] args) {
String phone = "18926119073";
Pattern pattern = Pattern.compile("1([358][0-9]|4[579]|66
|7[0135678]|9[89])[0-9]{8}");
Matcher matcher = pattern.matcher(phone);
System.out.println(matcher.matches());
}
}
複製程式碼
組
組是用括號劃分的正規表示式,可以根據組的編號來引用某個組。組號為0表示整個表示式,組號1表示被第一對括號括起的組,依次類推。因此,在下面這個表示式:
A(B(C))D
複製程式碼
中有三個組:組0是ABCD,組1是BC,組2是C。
Mather提供了很多有用的方法具體使用檢視API,使用Mather的替換方法可以實現隱藏手機號中間數字、隱藏使用者名稱等。
String phone = "18926119073";
Pattern pattern = Pattern.compile("(\\d{3})\\d{4}(\\d{4})");
Matcher matcher = pattern.matcher(phone);
phone = matcher.replaceAll("$1****$2");
System.out.println(phone);
複製程式碼
用正規表示式掃描
Java SE5新增類Scanner類,它可以大大減輕掃描輸入的工作。
public class ScannerRead {
public static void main(String[] args) {
Scanner scanner = new Scanner("Sir Robin of Camelot
\n22 1.61803"));
System.out.println("What is your name?");
String name = scanner.nextLine();
System.out.println(name);
System.out.println("(input: <age> <double>)");
System.out.println(scanner.nextInt()+" "+ scanner.nextDouble());
}
}
複製程式碼
Scanner的構造期可以接受任何型別的輸入物件,包括File物件、InputStream、String或者Readable物件。
Scanner所有的輸入、分詞以及翻譯的操作都隱藏在不同型別的next方法中,普通的next()方法返回下一個String,所有的基本型別(除char之外)都有對應的next方法,包括BigDecimal和BigInteger。所有的next方法,只有在找到一個完整的分詞之後才會返回,hasNext方法用以判斷下一個輸入分詞是否所需的型別。
在預設的情況下,Scanner根據空白符對輸入進行分詞,但是你可以用正規表示式指定自己所需的定界符:
public class ScannerDelimiter {
public static void main(String[] args) {
Scanner scanner = new Scanner("12,42,78,99");
scanner.useDelimiter(",");
while (scanner.hasNextInt()){
System.out.println(scanner.nextInt());
}
}
}
複製程式碼
除了能夠掃描基本型別之外,你還可以使用自定義的正規表示式進行掃描。如下所示:
public class ThreatAnalyzer {
static String threatData =
"58.27.82.161@02/10/2005\n" +
"124.45.82.161@02/10/2005\n" +
"58.27.82.161@02/10/2005\n" +
"72.27.82.161@02/10/2005\n" +
"[Next log section with different data format]";
public static void main(String[] args) {
Scanner scanner = new Scanner(threatData);
String pattern = "(\\d+[.]\\d+[.]\\d+[.]\\d+)@(\\d{2}/\\d{2}/\\d{4})";
while (scanner.hasNext(pattern)) {
scanner.next(pattern);
MatchResult match = scanner.match();
String ip = match.group(1);
String date = match.group(2);
System.out.format("Threat on %s from %s\n", date, ip);
}
}
}
複製程式碼