字串陣列
6.1.1 String 類別
由字元所組成的一串文字符號被稱之為「字串」,例如 "Hello" 這個字串就是由 'H'、'e'、'l'、'l'、'o' 這五個字元所組成,在某些程式語言中,字串是以
字元陣列
的方式存在
String 物件上的幾個方法
方法 說明 length() 取得字串的字元長度 equals() 判斷原字串中的字元是否相等於指定字串中的字元 toLowerCase() 轉換字串中的英文字元為小寫 toUpperCase() 轉換字串中的英文字元為大寫
將字串剖析為數值型態
方法 說明 Byte.parseByte(字串) 將字串剖析為位元 Short.parseShort(字串) 將字串剖析為short整數 Integer.parseInt(字串) 將字串剖析為int整數 Long.parseLong(字串) 將字串剖析為long整數 Float.parseFloat(字串) 將字串剖析為float浮點數 Double.parseDouble(字串) 將字串剖析為double浮點數
注意如果指定的字串無法剖析為指定的資料型態數值,則會發生 NumberFormatException 例外。 之前宣告字串時,都是以這樣的樣子來宣告:
String str = "caterpillar";
這樣的宣告方式看來像是基本資料型態宣告,但事實上 String 並不是 Java 的基本資料型態,String 是 java.lang 套件下所提供的類別,如果以配置物件的觀念來宣告字串,應該是這樣的:
String str = new String("caterpillar");
取得字串中的字元之方法
方法 說明 char charAt(int index) 傳回指定索引處的字元 int indexOf(int ch) 傳回指定字元第一個找到的索引位置 int indexOf(String str) 傳回指定字串第一個找到的索引位置 int lastIndexOf(int ch) 傳回指定字元最後一個找到的索引位置 String substring(int beginIndex) 取出指定索引處至字串尾端的子字串 String substring(int beginIndex, int endIndex) 取出指定索引範圍子字串 char[] toCharArray() 將字串轉換為字元陣列
public class CharAtString { public static void main(String[] args) { String text = "One's left brain has nothing right.\n"
+ "One's right brain has nothing left.\n";
System.out.println("字串內容: ");
for(int i = 0; i < text.length(); i++)
System.out.print(text.charAt(i));
System.out.println("\n第一個left: " +
text.indexOf("left"));
System.out.println("最後一個left: " +
text.lastIndexOf("left"));
char[] charArr = text.toCharArray();
System.out.println("\n字元Array內容: ");
for(int i = 0; i < charArr.length; i++)
System.out.print(charArr[i]);
}
}
執行結果:
字串內容: One's left brain has nothing right. One's right brain has nothing left.
第一個left: 6 最後一個left: 66
字元Array內容: One's left brain has nothing right. One's right brain has nothing left.
在建構字串物件時,除了直接在 '=' 後使用 "" 來指定字串常數之外,您也可以使用字元陣列來建構,例如使用字元陣列 name,建構出一個內容為 "caterpillar" 的字串:
char[] name = {'c', 'a', 't', 'e', 'r', 'p', 'i', 'l', 'l', 'a', 'r'};
String str = new String(name);
除了以上所介紹的幾個方法之外,您應該查詢 API 手冊,瞭解更多有關於 String 類別的方法,例如 String 上還有 endsWith() 方法可以判斷字串是不是以指定的文字作為結束,您可以使用這個方法來過濾檔案名稱,範例 6.3 就使用 endsWith() 來過濾出副檔名為 jpg 的檔案。 範例6.3 FileFilter.java
public class FileFilter { public static void main(String[] args) { String[] filenames = {"caterpillar.jpg", "cater.gif", "bush.jpg", "wuwu.jpg", "clockman.gif"};
System.out.print("過濾出jpg檔案: ");
for(int i = 0; i < filenames.length; i++) {
if(filenames[i].endsWith("jpg")) {
System.out.print(filenames[i] + " ");
}
}
System.out.println("");
}
}
執行結果:
過濾出jpg檔案: caterpillar.jpg bush.jpg wuwu.jpg
在 intern() 方法被呼叫時,如果池(Pool)中已經包括了相同的 String 物件(相同與否由 equals() 方法決定),那麼會從池中返回該字串,否則的話原 String 物件會被加入池中,並返回這個 String 物件的參考。
這段話其實說明了「Flyweight模式」(請見本章後的網路索引) 的運作方式,直接使用範例 6.4 來說明會更清楚。 範例 6.4 InternString.java
public class InternString { public static void main(String[] args) { String str1 = "fly"; String str2 = "weight"; String str3 = "flyweight"; String str4 = null;
str4 = str1 + str2;
System.out.println(str3 == str4);
str4 = (str1 + str2).intern();
System.out.println(str3 == str4);
}
}
使用圖形來說明這個範例,在宣告 str1、str2、str3 後,String 池中的狀況如圖 6.4 所示。
字串池示意
圖 6.4 字串池示意
在 Java 中,使用 '+' 串接字串的話會產生一個新的字串物件,所以在程式中第一次比較 str3 與 str4 物件是否為同一物件時,結果會是 false,如圖 6.5 所示。
字串加法會產生新的字串
圖 6.5 字串加法會產生新的字串
intern() 方法會先檢查 String 池中是否存在字元部份相同的字串物件,如果有的話就傳回,由於程式中之前已經有宣告 "flyweight" 字串物件,intern() 在 String 池中發現了它,所以直接傳回,這時再進行比較,str3 與 str4 所指向的其實會是同一物件,所以結果會是 true,如圖 6.6所示。
intern() 會返回 String 池中字串物件之參考
6.1.3 StringBuilder 類別
一個 String 物件的長度是固定的,您不能改變它的內容,或者是附加新的字元至 String 物件中,您也許會使用 '+' 來串接字串以達到附加新字元或字串的目的,但 '+' 會產生一個新的 String 實例,如果您的程式對這種附加字串的需求很頻繁,並不建議使用 '+' 來進行字串的串接,在物件導向程式設計中,最好是能重複運用已生成的物件,物件的生成需要記憶體空間與時間,不斷的產生 String 實例是一件沒有效率的行為。
在 J2SE 5.0 開始提供 java.lang.StringBuilder 類別,使用這個類別所產生的物件預設會有 16 個字元的長度,您也可以自行指定初始長度,如果附加的字元超出可容納的長度,則 StringBuilder 物件會自動增加長度以容納被附加的字元,如果您有頻繁作字串附加的需求,使用 StringBuilder 會讓程式的效率大大提昇,來寫個簡單的測試程式就可以知道效能差距有多大。
範例 6.5 AppendStringTest.java
public class AppendStringTest { public static void main(String[] args) { String text = "";
long beginTime = System.currentTimeMillis();
for(int i = 0; i < 10000; i++)
text = text + i;
long endTime = System.currentTimeMillis();
System.out.println("執行時間:" + (endTime - beginTime));
StringBuilder builder = new StringBuilder("");
beginTime = System.currentTimeMillis();
for(int i = 0; i < 10000; i++)
builder.append(String.valueOf(i));
endTime = System.currentTimeMillis();
System.out.println("執行時間:" + (endTime - beginTime));
}
}
在範例 6.5 中首先使用 '+' 來串接字串,您使用 System.currentTimeMillis() 取得 for 迴圈執行前、後的系統時間,如此就可以得知 for 迴圈執行了多久,以下是我的電腦上的測試數據:
執行時間:4641 執行時間:16
您可以看到執行的時間差距很大,這說明了使用 '+' 串接字串所帶來負擔,如果您有經常作附加字串的需求,建議使用 StringBuilder,事實上就範例 6.5 來說,第二個 for 迴圈執行時間還可以更短,因為 append() 也可以接受基本資料型態,所以不必特地使用 String.valueOf() 方法從 int 取得 String,改為以下的方式,執行時間可以更大幅縮短:
for(int i = 0; i < 10000; i++) builder.append(i);
使用 StringBuilder 最後若要輸出字串結果,可以呼叫其 toString() 方法,您可以使用 length() 方法得知目前物件中的字元長度,而 capacity() 可傳回該物件目前可容納的字元容量,另外 StringBuilder 還有像是 insert() 方法可以將字元插入指定的位置,如果該位置以後有字元,則將所有的字元往後移;deleteChar() 方法可以刪除指定位置的字元,而 reserve() 方法可以反轉字串,詳細的使用可以查詢看看 java.lang.StringBuilder 的 API 文件說明。
StringBuilder 是 J2SE 5.0 才新增的類別,在 J2SE 5.0 之前的版本若有相同的需求,是使用 java.lang.StringBuffer,事實上,StringBuilder 被設計為與 StringBuffer 具有相同的操作介面,在單機非「多執行緒」(Multithread)的情況下使用 StringBuilder 會有較好的效率,因為 StringBuilder 沒有處理「同步」(Synchronized)問題;StringBuffer 則會處理同步問題,如果您的 StringBuilder 會在多執行緒下被操作,則要改用 StringBuffer,讓物件自行管理同步問題,關於多執行緒的觀念,會在第 15 章詳細說明。
6.2 字串進階運用
6.2.3 使用正則表示式(Regular expression)
如果您查詢 J2SE 1.4 之後的 String 線上 API 手冊說明,您會發現有 matches()、replaceAll() 等方法,您所傳入的引數是「正則表示式」(Regular expression)的字串,正則表示式最早是由數學家 Stephen Kleene 于 1956 年提出,主要使用在字元字串的格式比對,後來在資訊領域廣為應用,現在已經成為 ISO(國際標準組織)的標準之一。
Java 在 J2SE 1.4 之後開始支援正則表示式,您可以在 API 文件的 java.util.regex.Pattern 類別中找到支援的正則表示式相關資訊;您可以將正則表示式應用於字串的比對、取代、分離等動作上,以下將介紹幾個簡單的正則表示式。
對於一些簡單的字元比對,例如 1 到 9、A-Z 等,您可以使用預先定義的符號來表示,表 6.4 列出幾個常用的字元比對符號。 表 6.4 字元比對符號 方法 說明 . 符合任一字元 \d 符合 0 到 9 任一個數字字元 \D 符合 0-9 以外的字元 \s 符合 '\t'、'\n'、'\x0B'、'\f'、'\r' 等空白字元 \w 符合 a 到 z、A 到 Z、0 到 9 等字元,也就是數字或是字母都符合 \W 符合 a 到 z、A 到 Z、0 到 9 等之外的字元,也就是除數字與字母外都符合
6.2.2 分離字串
將字串依所設定的條件予以分離是很常見的操作,例如指令的分離、文字檔案的資料讀出等,以後者而言,當您在文字檔案中儲存以下的資料時,在讀入檔案後,將可以使用 String 的 split() 來協助每一格的資料分離。
justin 64/5/26 0939002302 5433343 momor 68/7/23 0939100391 5432343
範例 6.8是個簡單的示範,假設 fakeFileData 的資料就是檔案中讀入的文字資料。 範例 6.8 SplitStringDemo.java
public class SplitStringDemo { public static void main(String args[]) { String[] fakeFileData = { "justin\t64/5/26\t0939002302\t5433343", "momor\t68/7/23\t0939100391\t5432343" }; for(String data : fakeFileData) { String[] tokens = data.split("\t"); for(String token : tokens) { System.out.print(token + "\t| "); } System.out.println(); } } }
執行結果:
justin | 64/5/26 | 0939002302 | 5433343 | momor | 68/7/23 | 0939100391 | 5432343 |
split() 依您所設定的分隔設定,將字串分為數個子字串並以 String 陣列傳回
,這邊簡單的介紹了一下 split() 方法的使用,有些用過 Java 的人可能會想到 java.util.StringTokenizer,基本上 API 文件中明確的表示 StringTokenizer 已經是「遺產類別」(Legacy class)了,存在的原因是為了與舊版 Java 程式的相容性,不建議在您撰寫新的 Java 程式時使用,使用 split() 來代替會是個好的方案,而且您還可以進一步搭配正則表示式來進行字串分離。
字元比對符號 方法 說明 . 符合任一字元 \d 符合 0 到 9 任一個數字字元 \D 符合 0-9 以外的字元 \s 符合 '\t'、'\n'、'\x0B'、'\f'、'\r' 等空白字元 \w 符合 a 到 z、A 到 Z、0 到 9 等字元,也就是數字或是字母都符合 \W 符合 a 到 z、A 到 Z、0 到 9 等之外的字元,也就是除數字與字母外都符合
舉例來說,如果有一字串 "abcdebcadxbc",使用 ".bc" 來作比對的話,符合的子字串有 "abc"、"ebc"、"xbc" 三個;如果使用 "..cd",則符合的子字串只有 "abcd",範例 6.9證實這個說明。 範例6.9 RegularExpressionDemo.java
public class RegularExpressionDemo { public static void main(String[] args) { String text = "abcdebcadxbc";
String[] tokens = text.split(".bc");
for(String token : tokens) {
System.out.print(token + " ");
}
System.out.println();
tokens = text.split("..cd");
for(String token : tokens) {
System.out.print(token + " ");
}
System.out.println();
}
}
執行結果:
d ad ebcadxbc
使用 ".bc" 來作比對的話,由於符合的子字串有 "abc"、"ebc"、"xbc" 三個,所以 split() 方法會使用這三個字串為依據來作字串分離,傳回的自然就是不符合表示式 ".bc" 的 "d" 與 "ad",同理如果表示式為 "..cd",則使用 split() 傳回的就是不符合 "..cd" 的 "ebcadxbc"。
您也可以使用「字元類」(Character class)來比較一組字元範圍,表 6.5 示範了幾個字元類的設定方式。 表 6.5 字元類範例 範例 作用 [abc] 符合 a、b 或 c abc 符合「a 或 b 或 c」之外的字元 [a-zA-Z] 符合 a 到 z 或者是 A 到 Z 的字元 [a-d[m-p]] a 到 d 或者是m 到 p,也可以寫成 [a-dm-p] [a-z&&[def]] a 到 z 並且是 d 或 e 或 f,結果就是 d 或 e 或 f 可以符合 [a-z&&bc] a 到 z 並且不是 b 或 c [a-z&&m-p] a 到 z 並且不是 m 到 p
指定一個字元之外,您也可以加上「貪婪量詞」(Greedy quantifiers)來指定字元可能出現的次數,表 6.6 示範了幾個例子。 表 6.6 貪婪量詞範例 範例 作用 X? X 可出現一次或完全沒有 X* X 可出現零次或多次 X+ X 可出現一次或多次 X{n} X 可出現 n 次 X{n,} X 可出現至少n次 X{n, m} X 可出現至少 n 次,但不超過 m 次 X? X 可出現一次或完全沒有
另外還有 Reluctant quantifiers、Possessive quantifiers 等的指定,您可以自行參考 java.util.regex.Pattern 類別 API 文件中的說明。
在 String 類別中,matches() 方法可以讓您驗證字串是否符合指定的正則表示式,這通常用於驗證使用者輸入的字串資料是否正確,例如電話號碼格式;replaceAll() 方法可以將符合正則表示式的子字串置換為指定的字串;split() 方法可以讓您依指定的正則表示式,將符合的子字串排除,剩下的子字串分離出來並以字串陣列傳回,範例 6.9 已經示範了 split() 方法的使用,接下來在範例 6.10 中示範了 replaceAll() 與 matches() 方法的運用。
在 String 類別中,matches() 方法可以讓您驗證字串是否符合指定的正則表示式,這通常用於驗證使用者輸入的字串資料是否正確,例如電話號碼格式;replaceAll() 方法可以將符合正則表示式的子字串置換為指定的字串;split() 方法可以讓您依指定的正則表示式,將符合的子字串排除,剩下的子字串分離出來並以字串陣列傳回,範例 6.9 已經示範了 split() 方法的使用,接下來在範例 6.10 中示範了 replaceAll() 與 matches() 方法的運用。 範例 6.10 UseRegularExpression.java
import java.io.*;
public class UseRegularExpression { public static void main(String args[]) throws IOException { BufferedReader reader = new BufferedReader( new InputStreamReader(System.in));
System.out.println("abcdefgabcabc".replaceAll(".bc", "###"));
** String phoneEL = "[0-9]{4}-[0-9]{6}";
//電話號碼驗證
String urlEL = "<a.+href=['\"]?.?['\"]?.?>";
//URL驗證
String emailEL = "^[_a-z0-9-]+(.[_a-z0-9-]+)*" +
"@[a-z0-9-]+([.][a-z0-9-]+)*$";**
//EMAIL驗證
System.out.print("輸入手機號碼: ");
String input = reader.readLine();
if(input.matches(phoneEL))
System.out.println("格式正確");
else
System.out.println("格式錯誤");
System.out.print("輸入href標籤: ");
input = reader.readLine();
// 驗證href標籤
if(input.matches(urlEL))
System.out.println("格式正確");
else
System.out.println("格式錯誤");
System.out.print("輸入電子郵件: ");
input = reader.readLine();
// 驗證電子郵件格式
if(input.matches(emailEL))
System.out.println("格式正確");
else
System.out.println("格式錯誤");
}
}
執行結果:
defg
輸入手機號碼: 0939-100391 格式正確 輸入href標籤: 格式正確 輸入電子郵件: [email protected] 格式正確
反斜線符號()又稱為逸出字元(escape character), 這個逸出字元指示System.out物件的println方法輸出「特殊字元」。 當反斜線出現在字串中時,Java會將反斜線和它後面的一個字元組合成逸出序列。 以下將介紹常用的逸出序列:
- \n 換行字元,螢幕游標將移到下一行的開始處。
- \t 水平定位鍵,螢幕游標移到下一個定位點(相當於3個空白鍵的距離)。
- \r 歸位字元,螢幕游標移到目前這一行的開端處。
在歸位字元後所輸出的字串會覆蓋掉先前該行所輸出的字串。
- \ 用來列印反斜線符號。
- \" 用來列印雙引號。
6.2.4 Pattern、Matcher
String 上可使用正則表示式的操作,實際上是利用了 java.util.regex.Pattern 與 java.util.regex.Matcher 的功能,當您呼叫 String 的 matches() 方法時,實際上是呼叫 Pattern 的靜態方法 matches(),這個方法會傳回 boolean 值,表示字串是否符合正則表示式。
import java.util.regex.*;
public class UsePatternMatcher { public static void main(String[] args) { String phones1 = "Justin 的手機號碼:0939-100391\n" + "momor 的手機號碼:0939-666888\n";
Pattern pattern = Pattern.compile(".*0939-\\d{6}");
Matcher matcher = pattern.matcher(phones1);
while(matcher.find()) {
System.out.println(matcher.group());
}
String phones2 =
"caterpillar 的手機號碼:0952-600391\n" +
"bush 的手機號碼:0939-550391";
matcher = pattern.matcher(phones2);
while(matcher.find()) {
System.out.println(matcher.group());
}
}
}
來使用 Pattern 與 Matcher 改寫一下範例 6.9,讓程式可以傳回符合正則式的字串,而不是傳回不符合的字串。 範例 6.12 RegularExpressionDemo2.java
import java.util.regex.*;
public class RegularExpressionDemo2 { public static void main(String[] args) { String text = "abcdebcadxbc";
Pattern pattern = Pattern.compile(".bc");
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
System.out.println(matcher.group());
}
System.out.println();
}
}
執行結果:
abc ebc xbc