從概念上講,Java 字符串就是 Unicode 字符序列。Java 沒有內(nèi)置的字符串類型,而是在標(biāo)準(zhǔn) Java 類庫(kù)中提供了一個(gè)預(yù)定義類,很自然地叫做 String。每個(gè)用雙引號(hào)括起來(lái)的字符串都是 String 類的一個(gè)實(shí)例:
String e = ""; // 空字符串
String greeting = "Hello";
1. 子串
String 類的 substring 方法可以從一個(gè)較大的字符串提取出一個(gè)子串。
String greeting = "Hello";
String s = greeting.substring(0, 3); // 變量 s 為 "Hel"
字符串中的代碼單元和代碼點(diǎn)從 0 開始計(jì)算。substring 方法的第二個(gè)參數(shù)是不想復(fù)制的第一個(gè)位置。
substring 的工作方式有一個(gè)優(yōu)點(diǎn): 容易計(jì)算子串長(zhǎng)度。字符串 s.substring(a, b) 的長(zhǎng)度為 b - a。
2. 拼接
Java 語(yǔ)言允許使用 + 號(hào)連接(拼接)兩個(gè)字符串。
String expletive = "Expletive";
String PG13 = "deleted";
String message = expletive + PG13;
System.out.println(message); // 打印 Expletivedeleted
當(dāng)將一個(gè)字符串與一個(gè)非字符串的值進(jìn)行拼接時(shí),后者被轉(zhuǎn)換成字符串。
如果需要把多個(gè)字符串放在一起,用一個(gè)界定符分隔,可以使用靜態(tài) join 方法:
String all = String.join(" /", "S", "M", "L", "XL");
System.out.println(all);// 打印 S /M /L /XL
String [] arr = {"S", "M", "L", "XL"};
all = String.join(" /", arr);
System.out.println(all);// 打印 S /M /L /XL
arr = new String[]{"S", "M", "L", "XL"};
List<String> list = Arrays.asList(arr);
all = String.join(" /", arr);
System.out.println(all);// 打印 S /M /L /XL
3. 字符串不可變
String 類沒有提供用于修改字符串的方法。由于不能修改 Java 字符串,所以在 Java 文檔中將 String 類對(duì)象稱為是不可變的(immutable)。不可變字符串卻有一個(gè)優(yōu)點(diǎn):編譯器可以讓字符串共享。
Java 的設(shè)計(jì)者認(rèn)為共享帶來(lái)的高效率遠(yuǎn)遠(yuǎn)勝過于提取子串、拼接字符串所帶來(lái)的低效率。查看一下程序會(huì)發(fā)現(xiàn):很少需要修改字符串,而是往往需要對(duì)字符串進(jìn)行比較(有一種例外情況,將來(lái)自于文件或鍵盤的單個(gè)字符或較短的字符串匯集成字符串),Java 專門為此提供了一個(gè)單獨(dú)的類。
4. 檢測(cè)字符串是否相等
可以使用 equals 方法檢測(cè)兩個(gè)字符串是否相等。表達(dá)式:
s.equals(t);
如果字符串 s 與字符串 t 相等,返回 true;否則,返回 false。s 與 t 可以是字符串變量,也可以是字符串字面量。
想要檢測(cè)兩個(gè)字符串是否相等,而不區(qū)分大小寫,可以使用 equalsIgnoreCase 方法。
"Hello".equalsIgnoreCase("hello"); // true
一定不要使用 == 運(yùn)算符檢測(cè)兩個(gè)字符串是否相等!這個(gè)運(yùn)算符只能夠確定兩個(gè)字符串是否放置在同一個(gè)位置上。當(dāng)然,如果字符串放置在同一個(gè)位置上,她們必然相等。但是,完全有可能將內(nèi)容相同的多個(gè)字符串的拷貝放置在不同的位置上。
如果虛擬機(jī)始終將相同的字符串共享,就可以使用 == 運(yùn)算符檢測(cè)是否相等。但實(shí)際上只有字符串常量是共享的。而 + 或 substring 等操作產(chǎn)生的結(jié)果并不是共享的。因此,千萬(wàn)不要使用 == 運(yùn)算符測(cè)試字符串的相等性,以免在程序中出現(xiàn)糟糕的 bug。這種 bug 很像隨機(jī)產(chǎn)生的間歇性錯(cuò)誤。
5. 空串與 Null 串
空串是一個(gè) Java 對(duì)象,有自己的長(zhǎng)度(0)和內(nèi)容(空)??沾?"" 是長(zhǎng)度為 0 的字符串。
代碼檢查一個(gè)字符串是否為空:
if (str.length() == 0)
// 或
if (str.equals(""))
要檢查一個(gè)字符串是否為 null:
if (str == null)
檢查一個(gè)字符串既不是 null 也不為空串:
if (str != null && str.length() != 0)
6. 碼點(diǎn)與代碼單元
Java 字符串由 char 值序列組成。char 數(shù)據(jù)類型是一個(gè)采用 UTF-16 編碼表示 Unicode 碼點(diǎn)的代碼單元。最常用 Unicode 字符使用一個(gè)代碼單元就可以表示,而輔助字符需要一對(duì)代碼單元表示。
length 方法將返回采用 UTF-16 編碼表示的給定字符所需要的代碼單元數(shù)量。
String greeting = "Hello";
int n = greeting.length(); // 變量 n 為 5
要想得到實(shí)際的長(zhǎng)度,即碼點(diǎn)數(shù)量,可調(diào)用:
String greeting = "Hello";
int n = greeting.codePointCount(0, greeting.length()); // 變量 n 為 5
調(diào)用 s.charAt(n) 將返回位置 n 的代碼單元,n 介于 0 ~ s.length()-1 之間。
String greeting = "Hello";
char first = greeting.charAt(0); // first 為 'H'
char last = greeting.charAt(4); // last 為 'o'
要想得到第 i 個(gè)代碼點(diǎn),應(yīng)該使用下列語(yǔ)句
String greeting = "Hello";
int i = 3;
int index = greeting.offsetByCodePoints(0, i);
int cp = greeting.codePointAt(index);
// 或者
int cp = greeting.codePointAt(index);
Java 對(duì)字符串中的代碼單元和碼點(diǎn)從 0 開始。
為什么對(duì)代碼單元如此大驚小怪?考慮下列語(yǔ)句:
?? is the set of octonions.
使用 UTF-16編碼表示字符 ??(U+1D546) 需要兩個(gè)代碼單元。調(diào)用
char ch = sentence.charAt(1);
返回不是一個(gè)空格,而是 ?? 的第二個(gè)代碼單元。為了避免這個(gè)問題,不要使用 char 類型。這太底層了。
String greeting = "Hello";
// 獲得實(shí)際的長(zhǎng)度,即碼點(diǎn)數(shù)量
int n = greeting.codePointCount(0, greeting.length());
System.out.println(n); // 打印 5
// 輔助字符 ??(U+1D546)
String str = "\ud835\udd46";
System.out.println(str); // 打印 ??
// 獲得輔助字符 ?? 實(shí)際的長(zhǎng)度,即碼點(diǎn)數(shù)量:2
n = str.length();
System.out.println(n); // 打印 2
System.out.println(str.charAt(0)); // 打印 ?
System.out.println(str.charAt(1)); // 打印 ?
// 打印輔助字符 ?? 的碼點(diǎn)
System.out.println(str.codePointAt(0)); // 打印 120134
// 打印輔助字符 ??
System.out.println(new String(Character.toChars(120134))); // 打印 ??
// 打印輔助字符 ?? 的 unicode 字符:\ud835\udd46
for(int i = 0; i < str.length(); i++) {
String unicode = Integer.toHexString(str.charAt(i));
System.out.print("\\u");
System.out.print(unicode);
}
如果想要遍歷一個(gè)字符串,并且依次查看每一個(gè)碼點(diǎn),可以使用下列語(yǔ)句:
String str = "\ud835\udd46 is the set of octonions.";
int i = 0;
while(i < str.length()) {
int cp = str.codePointAt(i);
System.out.print(new String(Character.toChars(cp)));
if(Character.isSupplementaryCodePoint(cp)) {
i += 2;
}else {
i++;
}
}
打印:?? is the set of octonions.
可以使用下列語(yǔ)句實(shí)現(xiàn)回退操作:
String str = "\ud835\udd46 is the set of octonions.";
int i = str.length();
while(i > 0) {
i--;
if (Character.isSurrogate(str.charAt(i))) {
i--;
}
int cp = str.codePointAt(i);
System.out.print(new String(Character.toChars(cp)));
}
打?。?code>.snoinotco fo tes eht si ??
顯然,這很麻煩。更容易的辦法是使用 codePoints 方法,它會(huì)生成一個(gè) int 值 “流”,每個(gè) int 值對(duì)應(yīng)一個(gè)碼點(diǎn)??梢詫⑺D(zhuǎn)換為一個(gè)數(shù)組,再完成遍歷。
int[] codePoints = str.codePoints().toArray();
反之,要把一個(gè)碼點(diǎn)數(shù)組轉(zhuǎn)換為一個(gè)字符串,可以使用構(gòu)造函數(shù)。
String str = new String(codePoints, 0, codePoints.length);
虛擬機(jī)不一定吧字符串實(shí)現(xiàn)為代碼單元序列。在 Java 9 中,只包含單字節(jié)代碼單元的字符串使用 byte 數(shù)組實(shí)現(xiàn),所有其他字符串使用 char 數(shù)組。
// str = ?? is the set of octonions. ??????
String str = "\ud835\udd46 is the set of octonions. \ud83c\udf7a\ud83c\udf7a\ud83c\udf7a";
System.out.println(str);
// 正向遍歷字符串 1
for(int i = 0; i < str.length();) {
int cp = str.codePointAt(i);
if(Character.isSupplementaryCodePoint(cp)) {
i += 2;
}else {
i++;
}
char[] chars = Character.toChars(cp);
String code = new String(chars);
System.out.print(code);
}
System.out.println();
// 正向遍歷字符串 2
int[] codePoints = str.codePoints().toArray();
for(int i = 0; i < codePoints.length; i++) {
String code = new String(Character.toChars(codePoints[i]));
System.out.print(code);
}
System.out.println();
// 將碼點(diǎn)數(shù)組轉(zhuǎn)化為字符串
String newStr = new String(codePoints, 0, codePoints.length);
System.out.println(newStr);
// 反向遍歷字符串
for(int i = str.length(); i > 0;) {
i--;
char ch = str.charAt(i);
if(Character.isSurrogate(ch)) {
i--;
}
int cp = str.codePointAt(i);
char[] chars = Character.toChars(cp);
String code = new String(chars);
System.out.print(code);
}
7. 構(gòu)建字符串
有時(shí)需要由較短的字符串構(gòu)建字符串,例如,按鍵或來(lái)自文件中的單詞。采用字符串連接的方式達(dá)到此目的效率比較低。每次連接字符串,都會(huì)構(gòu)建一個(gè)新的 String 對(duì)象,即耗時(shí),又浪費(fèi)空間。使用 StringBuilder 類就可以避免上述問題的發(fā)生。
// 構(gòu)建一個(gè)空的字符串構(gòu)建器
StringBuilder builder = new StringBuilder();
// 向字符串構(gòu)造器對(duì)象中追加小段字符串
builder.append("Welcome");
builder.append(" ");
builder.append("to");
builder.append(" ");
builder.append("xiang017");
builder.append("!");
// 構(gòu)造字符串對(duì)象
String str = builder.toString();
// 打印 Welcome to xiang017!
System.out.println(str);
StringBuilder 類的前身是 StringBuffer,它的效率稍有些低,但允許采用多線程的方式添加或刪除字符。如果所有字符串編輯操作都在單個(gè)線程中執(zhí)行(通常都是這樣),則應(yīng)該使用 StringBuilder。這兩個(gè)類的 API 是一樣的。
8. StringBuilder 類中的重要方法:
java.lang.StringBuilder
- StringBuilder()
構(gòu)造一個(gè)空的字符串構(gòu)建器。
- int length()
返回構(gòu)建器或緩沖器中的代碼單元數(shù)量。
- StringBuilder append(String str)
追加一個(gè)字符串并返回 this。
- StringBuiler append(char c)
追加一個(gè)代碼單元病分會(huì) this。
- void setCharAt(int i, char c)
將第 i 個(gè)代碼單元設(shè)置為 c。
- StringBuilder insert(int offset, String str)
在 offset 位置插入一個(gè)字符串并返回 this。
- StringBuilder insert(int offset, char c)
在 offset 位置插入一個(gè)代碼單元并返回 this。
- StringBuilder delete(int startIndex, int endIndex)
刪除變異量從 startIndex 到 endIndex-1 的代碼單元并返回 this。
- String toString()
返回一個(gè)與構(gòu)建器或緩沖器內(nèi)容相同的字符串。
9. String 類中的重要方法
Java 中的 String 類包含了 50 多個(gè)方法。它們絕大多數(shù)都很有用,使用的評(píng)率非常高。
java.lang.String
- char charAt(int index)
返回給定位置的代碼單元。除非對(duì)底層的代碼單元感興趣,否則不需要調(diào)用這個(gè)方法。
- int codePointAt(int index) 5
返回從給定位置開始的碼點(diǎn)。
- int offsetByCodePoints(int startIndex, int cpCount) 5
返回從 startIndex 碼點(diǎn)開始,cpCount 個(gè)碼點(diǎn)后的碼點(diǎn)索引。
- int compareTo(String other)
按照字典順序,如果字符串位于 other 之前,返回一個(gè)負(fù)數(shù);如果為字符串 other 之后,返回一個(gè)整數(shù);如果兩個(gè)字符串相等,返回 0。
- IntString codePoints() 8
將這個(gè)字符串的碼點(diǎn)作為一個(gè)流返回。調(diào)用 toArray 將它們放在一個(gè)數(shù)組中。
- new String(int[] codePoints, int offset, int count) 5
用數(shù)組中從 offset 開始的 count 個(gè)碼點(diǎn)構(gòu)造一個(gè)字符串。
- boolean empty()
如果字符串為空,返回 true。
- boolean blank() 11
如果字符串由空字符串組成,返回 true。
- boolean equals(Other other)
如果字符串與 other 相等,返回 true。
- boolean equalsIgnoreCase(String other)
如果字符串與 other 相等(忽略大小寫),返回 true。
- boolean startsWith(String prefix)
如果字符串以 prefix 開頭,返回 true。
- boolean endWith(String suffix)
如果字符串以 suffix 結(jié)尾,返回 true。
- int indexOf(String str)
- int indexOf(String str, int fromIndex)
- int indexOf(int cp)
- int indexOf(int cp, int fromIndex)
返回與字符串 str 或碼點(diǎn) cp 匹配的第一個(gè)子串的開始位置。從索引 0 或 fromIndex 開始匹配。如果在原始字符串中不存在 str 或 cp,則返回 -1。
- int lastIndexOf(String str)
- int lastIndexOf(String str, int fromIndex)
- int lastIndexOf(int cp)
- int lastIndexOf(int cp, int fromIndex)
返回與字符串 str 或碼點(diǎn) cp 匹配的最后一個(gè)子串的開始位置。從原始字符串末尾或 fromIndex 開始匹配。
- int length()
返回字符串代碼單元的個(gè)數(shù)。
- int codePointCount(int startIndex, int endIndex) 5
返回 startIndex 和 endIndex-1 之間的碼點(diǎn)個(gè)數(shù)。
- String replace(CharSequence oldString, CharSequence newString)
返回一個(gè)新的字符串。這個(gè)字符串用 newString 代替原始字符串中所有的 oldString。可以用 String 或 StringBuilder 對(duì)象作為 CharSequence 參數(shù)。
- String substring(int beginIndex)
- String substring(int beginIndex, int endIndex)
返回一個(gè)新字符串。這個(gè)字符串包含原始字符串從 beginIndex 到字符串末尾或 endIndex-1 的素有代碼單元。
- String toLowerCase()
返回一個(gè)新的字符串。這個(gè)字符串將原始字符串中的大寫字母改為小寫。
- String toUpperCase()
返回一個(gè)新的字符串。這個(gè)字符串將原始字符串中的小寫字母改為大寫。
- String trim()
- String strip() 11
返回一個(gè)新字符串。這個(gè)字符串將刪除原始字符串頭部和尾部小于等于 U+0020 的字符(trim)或空格(strip)。
- String join(CharSequence delimiter, CharSequence... elements) 11
返回一個(gè)新字符串,用給定的定界符連接所有元素。
- String repeat(int count) 11
返回一個(gè)字符串,當(dāng)前字符串重復(fù) count 次。