Java 字元串簡介
- 2021 年 1 月 29 日
- 筆記
從概念上講,Java 字元串就是 Unicode 字元序列。Java 沒有內置的字元串類型,而是在標準 Java 類庫中提供了一個預定義類,很自然地叫做 String。每個用雙引號括起來的字元串都是 String 類的一個實例:
String e = ""; // 空字元串
String greeting = "Hello";
1. 子串
String 類的 substring 方法可以從一個較大的字元串提取出一個子串。
String greeting = "Hello";
String s = greeting.substring(0, 3); // 變數 s 為 "Hel"
字元串中的程式碼單元和程式碼點從 0 開始計算。substring 方法的第二個參數是不想複製的第一個位置。
substring 的工作方式有一個優點: 容易計運算元串長度。字元串s.substring(a, b)
的長度為b - a
。
2. 拼接
Java 語言允許使用 + 號連接(拼接)兩個字元串。
String expletive = "Expletive";
String PG13 = "deleted";
String message = expletive + PG13;
System.out.println(message); // 列印 Expletivedeleted
當將一個字元串與一個非字元串的值進行拼接時,後者被轉換成字元串。
如果需要把多個字元串放在一起,用一個界定符分隔,可以使用靜態 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 類對象稱為是不可變的(immutable)。不可變字元串卻有一個優點:編譯器可以讓字元串共享。
Java 的設計者認為共享帶來的高效率遠遠勝過於提取子串、拼接字元串所帶來的低效率。查看一下程式會發現:很少需要修改字元串,而是往往需要對字元串進行比較(有一種例外情況,將來自於文件或鍵盤的單個字元或較短的字元串彙集成字元串),Java 專門為此提供了一個單獨的類。
4. 檢測字元串是否相等
可以使用 equals 方法檢測兩個字元串是否相等。表達式:
s.equals(t);
如果字元串 s 與字元串 t 相等,返回 true;否則,返回 false。s 與 t 可以是字元串變數,也可以是字元串字面量。
想要檢測兩個字元串是否相等,而不區分大小寫,可以使用equalsIgnoreCase
方法。
"Hello".equalsIgnoreCase("hello"); // true
一定不要使用 == 運算符檢測兩個字元串是否相等!這個運算符只能夠確定兩個字元串是否放置在同一個位置上。當然,如果字元串放置在同一個位置上,她們必然相等。但是,完全有可能將內容相同的多個字元串的拷貝放置在不同的位置上。
如果虛擬機始終將相同的字元串共享,就可以使用 == 運算符檢測是否相等。但實際上只有字元串常量是共享的。而 + 或 substring 等操作產生的結果並不是共享的。因此,千萬不要使用 == 運算符測試字元串的相等性,以免在程式中出現糟糕的 bug。這種 bug 很像隨機產生的間歇性錯誤。
5. 空串與 Null 串
空串是一個 Java 對象,有自己的長度(0)和內容(空)。空串 「」 是長度為 0 的字元串。
程式碼檢查一個字元串是否為空:
if (str.length() == 0)
// 或
if (str.equals(""))
要檢查一個字元串是否為 null:
if (str == null)
檢查一個字元串既不是 null 也不為空串:
if (str != null && str.length() != 0)
6. 碼點與程式碼單元
Java 字元串由 char 值序列組成。char 數據類型是一個採用 UTF-16 編碼表示 Unicode 碼點的程式碼單元。最常用 Unicode 字元使用一個程式碼單元就可以表示,而輔助字元需要一對程式碼單元表示。
length 方法將返回採用 UTF-16 編碼表示的給定字元所需要的程式碼單元數量。
String greeting = "Hello";
int n = greeting.length(); // 變數 n 為 5
要想得到實際的長度,即碼點數量,可調用:
String greeting = "Hello";
int n = greeting.codePointCount(0, greeting.length()); // 變數 n 為 5
調用 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 個程式碼點,應該使用下列語句
String greeting = "Hello";
int i = 3;
int index = greeting.offsetByCodePoints(0, i);
int cp = greeting.codePointAt(index);
// 或者
int cp = greeting.codePointAt(index);
Java 對字元串中的程式碼單元和碼點從 0 開始。
為什麼對程式碼單元如此大驚小怪?考慮下列語句:
𝕆 is the set of octonions.
使用 UTF-16編碼表示字元 𝕆(U+1D546) 需要兩個程式碼單元。調用
char ch = sentence.charAt(1);
返回不是一個空格,而是 𝕆 的第二個程式碼單元。為了避免這個問題,不要使用 char 類型。這太底層了。
String greeting = "Hello";
// 獲得實際的長度,即碼點數量
int n = greeting.codePointCount(0, greeting.length());
System.out.println(n); // 列印 5
// 輔助字元 𝕆(U+1D546)
String str = "\ud835\udd46";
System.out.println(str); // 列印 𝕆
// 獲得輔助字元 𝕆 實際的長度,即碼點數量:2
n = str.length();
System.out.println(n); // 列印 2
System.out.println(str.charAt(0)); // 列印 ?
System.out.println(str.charAt(1)); // 列印 ?
// 列印輔助字元 𝕆 的碼點
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);
}
如果想要遍歷一個字元串,並且依次查看每一個碼點,可以使用下列語句:
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.
可以使用下列語句實現回退操作:
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)));
}
列印:.snoinotco fo tes eht si 𝕆
顯然,這很麻煩。更容易的辦法是使用 codePoints 方法,它會生成一個 int 值 「流」,每個 int 值對應一個碼點。可以將它轉換為一個數組,再完成遍歷。
int[] codePoints = str.codePoints().toArray();
反之,要把一個碼點數組轉換為一個字元串,可以使用構造函數。
String str = new String(codePoints, 0, codePoints.length);
虛擬機不一定吧字元串實現為程式碼單元序列。在 Java 9 中,只包含單位元組程式碼單元的字元串使用 byte 數組實現,所有其他字元串使用 char 數組。
// 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();
// 將碼點數組轉化為字元串
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. 構建字元串
有時需要由較短的字元串構建字元串,例如,按鍵或來自文件中的單詞。採用字元串連接的方式達到此目的效率比較低。每次連接字元串,都會構建一個新的 String 對象,即耗時,又浪費空間。使用 StringBuilder 類就可以避免上述問題的發生。
// 構建一個空的字元串構建器
StringBuilder builder = new StringBuilder();
// 向字元串構造器對象中追加小段字元串
builder.append("Welcome");
builder.append(" ");
builder.append("to");
builder.append(" ");
builder.append("xiang017");
builder.append("!");
// 構造字元串對象
String str = builder.toString();
// 列印 Welcome to xiang017!
System.out.println(str);
StringBuilder 類的前身是 StringBuffer,它的效率稍有些低,但允許採用多執行緒的方式添加或刪除字元。如果所有字元串編輯操作都在單個執行緒中執行(通常都是這樣),則應該使用 StringBuilder。這兩個類的 API 是一樣的。
8. StringBuilder 類中的重要方法:
java.lang.StringBuilder
- StringBuilder()
構造一個空的字元串構建器。
- int length()
返回構建器或緩衝器中的程式碼單元數量。
- StringBuilder append(String str)
追加一個字元串並返回 this。
- StringBuiler append(char c)
追加一個程式碼單元病分會 this。
- void setCharAt(int i, char c)
將第 i 個程式碼單元設置為 c。
- StringBuilder insert(int offset, String str)
在 offset 位置插入一個字元串並返回 this。
- StringBuilder insert(int offset, char c)
在 offset 位置插入一個程式碼單元並返回 this。
- StringBuilder delete(int startIndex, int endIndex)
刪除變異量從 startIndex 到 endIndex-1 的程式碼單元並返回 this。
- String toString()
返回一個與構建器或緩衝器內容相同的字元串。
9. String 類中的重要方法
Java 中的 String 類包含了 50 多個方法。它們絕大多數都很有用,使用的評率非常高。
java.lang.String
- char charAt(int index)
返回給定位置的程式碼單元。除非對底層的程式碼單元感興趣,否則不需要調用這個方法。
- int codePointAt(int index) 5
返回從給定位置開始的碼點。
- int offsetByCodePoints(int startIndex, int cpCount) 5
返回從 startIndex 碼點開始,cpCount 個碼點後的碼點索引。
- int compareTo(String other)
按照字典順序,如果字元串位於 other 之前,返回一個負數;如果為字元串 other 之後,返回一個整數;如果兩個字元串相等,返回 0。
- IntString codePoints() 8
將這個字元串的碼點作為一個流返回。調用 toArray 將它們放在一個數組中。
- new String(int[] codePoints, int offset, int count) 5
用數組中從 offset 開始的 count 個碼點構造一個字元串。
- 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 結尾,返回 true。
- int indexOf(String str)
- int indexOf(String str, int fromIndex)
- int indexOf(int cp)
- int indexOf(int cp, int fromIndex)
返回與字元串 str 或碼點 cp 匹配的第一個子串的開始位置。從索引 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 或碼點 cp 匹配的最後一個子串的開始位置。從原始字元串末尾或 fromIndex 開始匹配。
- int length()
返回字元串程式碼單元的個數。
- int codePointCount(int startIndex, int endIndex) 5
返回 startIndex 和 endIndex-1 之間的碼點個數。
- String replace(CharSequence oldString, CharSequence newString)
返回一個新的字元串。這個字元串用 newString 代替原始字元串中所有的 oldString。可以用 String 或 StringBuilder 對象作為 CharSequence 參數。
- String substring(int beginIndex)
- String substring(int beginIndex, int endIndex)
返回一個新字元串。這個字元串包含原始字元串從 beginIndex 到字元串末尾或 endIndex-1 的素有程式碼單元。
- String toLowerCase()
返回一個新的字元串。這個字元串將原始字元串中的大寫字母改為小寫。
- String toUpperCase()
返回一個新的字元串。這個字元串將原始字元串中的小寫字母改為大寫。
- String trim()
- String strip() 11
返回一個新字元串。這個字元串將刪除原始字元串頭部和尾部小於等於 U+0020 的字元(trim)或空格(strip)。
- String join(CharSequence delimiter, CharSequence… elements) 11
返回一個新字元串,用給定的定界符連接所有元素。
- String repeat(int count) 11
返回一個字元串,當前字元串重複 count 次。