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 次。