1. 程式人生 > >《java程式設計思想》第十三章 字串

《java程式設計思想》第十三章 字串

1、不可變String

String物件時不可變的。每當把String物件作為方法的引數時,都會複製一份引用。(其實就是對函式中引數列表中引數的操作不會影響外面的原引數)

如下:

import static net.mindview.util.Print.*;
public class Immutable {
public static String upcase(String s) {
return s.toUpperCase();
}
public static void main(String[] args) {
String q = "howdy";
print(q); // howdy
String qq = upcase(q);
print(qq); // HOWDY
print(q); // howdy q還是q
}
} /* Output:
howdy
HOWDY
howdy



2、過載“+”與StringBuilder

用於String的“+”與“+=”是Java中僅有的兩個過載過的操作符,而Java並不允許程式設計師過載任何操作符。

在使用"+"操作符時,編譯器自動引入了java.lang.StringBuilder類,避免了連加情況下產生大量需要回收的垃圾(每+一次就會產生一個新的字串)。

因此,當為一個類編寫toString()方法時,如果字串操作簡單,可以信賴編譯器,它會為你合理的構造最終字串結果。但是如果要在toString()中使用迴圈,那麼最好自己建立一個StringBuilder物件,結合append()用它來構造最終結果

StringBuilder提供了豐富而全面的方法,包括insert()、replace()、subString()甚至reverse(),但最常用的還是append()和toString(),delete()方法。

StringBuilder是Java SE5引入的,在這之前還有Java的StringBuffer。StringBuffer是執行緒安全的,因此開銷會大一些。StringBuilder字串操作會更快一些。


3、無意識的遞迴

如果希望toString()方法打印出物件的記憶體地址,不可以使用this關鍵字。如果這樣寫:

public String toString() {
return " InfiniteRecursion address: " + this + "\n";
}



編譯器會試圖將"+"後面的this轉換為String,此時呼叫toString()函式,導致無窮遞迴。因此如果想要打印出記憶體地址,應該呼叫Object.toString()方法,所以呼叫super.toString()就可以了。

public String toString() {
return " InfiniteRecursion address: " + super.toString() + "\n";
}

結果是:

[ E02_RepairInfinite address: [email protected]
, E02_RepairInfinite address: [email protected]
, E02_RepairInfinite address: [email protected]
, E02_RepairInfinite address: [email protected]
, E02_RepairInfinite address: [email protected]
, E02_RepairInfinite address: [email protected]
, E02_RepairInfinite address: E02_Rep[email protected]
, E02_RepairInfinite address: [email protected]
, E02_RepairInfinite address: [email protected]
, E02_RepairInfinite address: [email protected]

]

4、String上的操作

String的一些基本方法:

一、建構函式
     String(byte[ ] bytes):通過byte陣列構造字串物件
     String(char[ ] value):通過char陣列構造字串物件
     String(Sting original):構造一個original副本。即:拷貝一個original
     String(StringBuffer buffer):通過StringBuffer陣列構造字串物件。

二、方法

1.  char   charAt(int index)  : 取字串中的某一個字元 ,其中的引數index指的是字串中序數。字串的序數從0開始到length()-1 。
    例如:String s = new String("abcdefghijklmnopqrstuvwxyz");
          System.out.println("s.charAt(5): " + s.charAt(5) );
          結果為: s.charAt(5): f
2.  int compareTo(String anotherString)  : 當前String物件與anotherString比較 相等 關係 返回0 不相等 時,從兩個字串第0個字元開始比較, 返回第一個不相等的字元差 ,另一種情況, 較長字串的前面部分恰巧是較短的字串,返回它們的長度差。
3.  int compareTo(Object o)  :如果o是String物件,和2的功能一樣;否則丟擲 ClassCastException 異常。
    例如:String s1 = new String("abcdefghijklmn");
            String s2 = new String("abcdefghij");
           String s3 = new String("abcdefghijalmn");
           System.out.println("s1.compareTo(s2): " + s1.compareTo(s2) ); //返回長度差
           System.out.println("s1.compareTo(s3): " + s1.compareTo(s3) ); //返回'k'-'a'的差
           結果為:s1.compareTo(s2): 4
                       s1.compareTo(s3): 10
4.  String concat(String str)  : 將該String物件與str連線在一起。
5.  boolean contentEquals(StringBuffer sb) 將該String物件與StringBuffer物件sb進行比較。
6.  static String copyValueOf(char[] data) 
7.  static String copyValueOf(char[] data, int offset, int count)  :這兩個方法 將char陣列轉換成String ,與其中一個建構函式類似。
8.  boolean endsWith(String suffix)  : 該String物件是否以suffix結尾
    例如:String s1 = new String("abcdefghij");
           String s2 = new String("ghij");
           System.out.println("s1.endsWith(s2): " + s1.endsWith(s2) );
           結果為:s1.endsWith(s2): true
9 . boolean equals(Object anObject)  : 當anObject不為空並且與當前String物件一樣,返回true;否則,返回false
10.  byte[] getBytes()  : 將該String物件轉換成byte陣列
11.  void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)  : 該方法將字串拷貝到字元陣列中 。其中,srcBegin為拷貝的起始位置、srcEnd為拷貝的結束位置、字串數值dst為目標字元陣列、dstBegin為目標字元陣列的拷貝起始位置。
     例如:char[] s1 = {'I',' ','l','o','v','e',' ','h','e','r','!'};//s1=I love her!
           String s2 = new String("you!"); s2.getChars(0,3,s1,7); //s1=I love you!
           System.out.println( s1 );
           結果為:I love you!
12.  int hashCode()  : 返回當前字元的雜湊表碼
13.  int indexOf(int ch)  : 只找第一個匹配字元位置
14.  int indexOf(int ch, int fromIndex)  : fromIndex開始找第一個匹配字元位置
15.  int indexOf(String str)  : 只找第一個匹配字串位置
16.  int indexOf(String str, int fromIndex)  : 從fromIndex開始找第一個匹配字串位置
      例如:String s = new String("write once, run anywhere!");
              String ss = new String("run");
              System.out.println("s.indexOf('r'): " + s.indexOf('r') );
              System.out.println("s.indexOf('r',2): " + s.indexOf('r',2) );
              System.out.println("s.indexOf(ss): " + s.indexOf(ss) );
              結果為:s.indexOf('r'): 1
                      s.indexOf('r',2): 12
                      s.indexOf(ss): 12
17.  int lastIndexOf(int ch)
18.  int lastIndexOf(int ch, int fromIndex)
19.  int lastIndexOf(String str)
20.  int lastIndexOf(String str, int fromIndex)  以上四個方法與13、14、15、16類似,不同的是:找 後一個匹配的內容
public class CompareToDemo {
      public static void main (String[] args) {
           String s1 = new String("ac b de b fg");
      
           System.out.println(s1.lastIndexOf((int)' b ', 7 ));
     }
}
執行結果 5
       (其中 fromIndex 的引數為  7 ,是從字串 acbdebfg 的最後一個字元 g 開始 往前數 的位數。既是從字元 c 開始匹配,尋找最後一個匹配 b 的位置。所以結果為  5


21.  int length()  : 返回當前字串長度
22.  String replace(char oldChar, char newChar)  : 將字元號串中第一個oldChar替換成newChar
23.  boolean startsWith(String prefix)  : 該String物件是否以prefix開始
24.  boolean startsWith(String prefix, int toffset)  : 該String物件從toffset位置算起,是否以prefix開始
     例如:String s = new String("write once, run anywhere!");
             String ss = new String("write");
             String sss = new String("once");
             System.out.println("s.startsWith(ss): " + s.startsWith(ss) );
             System.out.println("s.startsWith(sss,6): " + s.startsWith(sss,6) );
             結果為:s.startsWith(ss): true
                     s.startsWith(sss,6): true
25.  String substring(int beginIndex)  取從beginIndex位置開始到結束的子字串
26. String substring(int beginIndex, int endIndex)  : 取從beginIndex位置開始到endIndex位置的子字串
27.  char[ ] toCharArray()  : 將該String物件轉換成char陣列
28.  String toLowerCase()  : 將字串轉換成小寫
29.  String toUpperCase()  :將字串轉換成大寫。
     例如:String s = new String("java.lang.Class String");
             System.out.println("s.toUpperCase(): " + s.toUpperCase() );
             System.out.println("s.toLowerCase(): " + s.toLowerCase() );
             結果為:s.toUpperCase(): JAVA.LANG.CLASS STRING
                  s.toLowerCase(): java.lang.class string
30.  static String valueOf(boolean b)
31.  static String valueOf(char c)
32.  static String valueOf(char[] data)
33.  static String valueOf(char[] data, int offset, int count)
34.  static String valueOf(double d)
35.  static String valueOf(float f)
36.  static String valueOf(int i)
37.  static String valueOf(long l)
38.  static String valueOf(Object obj)

     以上方法用於將各種不同型別轉換成Java字元型。這些都是類方法。下面挑選一些上面常用的方法:

Java中String類的常用方法:

public char charAt(int index)
返回字串中第index個字元;
public int length()
返回字串的長度;
public int indexOf(String str)
返回字串中第一次出現str的位置;
public int indexOf(String str,int fromIndex)
返回字串從fromIndex開始第一次出現str的位置;
public boolean equalsIgnoreCase(String another)
比較字串與another是否一樣(忽略大小寫);
public String replace(char oldchar,char newChar)
在字串中用newChar字元替換oldChar字元
public boolean startsWith(String prefix)
判斷字串是否以prefix字串開頭;
public boolean endsWith(String suffix)
判斷一個字串是否以suffix字串結尾;
public String toUpperCase()
返回一個字串為該字串的大寫形式;
public String toLowerCase()
返回一個字串為該字串的小寫形式
public String substring(int beginIndex)
返回該字串從beginIndex開始到結尾的子字串;
public String substring(int beginIndex,int endIndex)
返回該字串從beginIndex開始到endsIndex結尾的子字串
public String trim()
返回該字串去掉開頭和結尾空格後的字串
public String[] split(String regex)
將一個字串按照指定的分隔符分隔,返回分隔後的字串陣列


5、格式化的輸出

Java SE5推出了格式化輸出這一功能。如下:

printf()

printf()並不是用過載的“+”操作符來連線引號內的字串,而是使用特殊的佔位符來表示資料將來的位置。而且它還將插入格式化字串的引數,以逗號分隔,排成一行。

printf("Row 1: [%d %f]\n", x, y);

這些佔位符稱作格式修飾符,它不但說明了插入位置,還說明了插入說明型別的變數,%d表示整數,%f表示浮點數,%s表示字串。


System.out.format()

Java SE5引入的format方法可用於PrintStream或者PrintWriter物件。其中也包括System.out物件。

format()方法模仿自C的printf(),兩者是等價的,以下展示三種方法輸出座標點:

public class SimpleFormat {
public static void main(String[] args) {
int x = 5;
double y = 5.332542;
// The old way:
System.out.println("Row 1: [" + x + " " + y + "]");
// The new way:
System.out.format("Row 1: [%d %f]\n", x, y);
// or
System.out.printf("Row 1: [%d %f]\n", x, y);
}
} /* Output:
Row 1: [5 5.332542]
Row 1: [5 5.332542]
Row 1: [5 5.332542]


Formatter類

在Java中,所有新的格式化功能都有java.util.Formatter處理。Formatter構造器經過過載可以接受多種輸出目的地,最常用的還是PrintStream()\OutputStream和File。

如下所示:

import java.io.*;
import java.util.*;
public class Turtle {
private String name;
private Formatter f;
public Turtle(String name, Formatter f) {
this.name = name;
this.f = f;
}
public void move(int x, int y) {
f.format("%s The Turtle is at (%d,%d)\n", name, x, y);
}
public static void main(String[] args) {
PrintStream outAlias = System.out;
Turtle tommy = new Turtle("Tommy",
new Formatter(System.out));
Turtle terry = new Turtle("Terry",
new Formatter(outAlias));
tommy.move(0,0);
terry.move(4,8);
tommy.move(3,4);
terry.move(2,5);
tommy.move(3,3);
terry.move(3,3);
}
} /* Output:
Tommy The Turtle is at (0,0)
Terry The Turtle is at (4,8)
Tommy The Turtle is at (3,4)
Terry The Turtle is at (2,5)
Tommy The Turtle is at (3,3)


tommy輸出到System.out中,terry輸出到System.out的一個別名中。


格式化說明符

為了在插入資料是控制空格和對齊,需要更加精細的格式修飾符。格式如下:

[argument_index$][flags][width][.precision]conversion

flags表示左右對齊,預設是右對齊,如果想左對齊就使用“-”標誌

with控制最小尺寸(寬度)至少該這麼長,不夠用空格替代。

在with後面加上"."後面表示精度precision。

用於String(%s)時,表示最多可寫的字元數

用於浮點數(%f)表示小數點後面的位數,多了舍入,少了補0,預設是6位

用於整數(%d)時,不可用於整數,會觸發異常


下面是例子:

import java.util.*;
public class Receipt {
private double total = 0;
private Formatter f = new Formatter(System.out);
public void printTitle() {
f.format("%-15s %5s %10s\n", "Item", "Qty", "Price");  //Item項都有“-”,表示左對齊,其餘項都是右對齊
f.format("%-15s %5s %10s\n", "----", "---", "-----");
}
public void print(String name, int qty, double price) {
f.format("%-15.15s %5d %10.2f\n", name, qty, price);    //name是左對齊並且最多15個字元,包括空格。price最多小數點後兩位。
total += price;
}
public void printTotal() {
f.format("%-15s %5s %10.2f\n", "Tax", "", total*0.06);
f.format("%-15s %5s %10s\n", "", "", "-----");
f.format("%-15s %5s %10.2f\n", "Total", "",
total * 1.06);
}
public static void main(String[] args) {
Receipt receipt = new Receipt();
receipt.printTitle();
receipt.print("Jack’s Magic Beans", 4, 4.25);
receipt.print("Princess Peas", 3, 5.1);
receipt.print("Three Bears Porridge", 1, 14.29);
receipt.printTotal();
}
} /* Output:
Item Qty Price
---- --- -----
Jack’s Magic Be 4 4.25
Princess Peas 3 5.10
Three Bears Por 1 14.29
Tax 1.42
-----
Total 25.06


Formatter轉換

常用的型別轉換:


d 整型 (十進位制)
c Unicode字元
Boolean

sString
浮點型 (十進位制)
浮點型(科學計數)
整數 (十六進位制)
雜湊碼(十六進位制)
字元"%"

例項:

Formatter f = new Formatter(System.out);
char u = ‘a’;
f.format("c: %c\n", u);
f.format("b: %b\n", u);


所有型別變數都可以執行b轉換,除了Boolean型別對應相應的true/false,其它的只要引數不為Null,結果永遠是true。(引數為數字0,仍然是true,這點與C不同)

String.format()

String.format()是一個static方法,它接受與Formatter.format()方法一樣的引數,但返回一個String物件。

如下所示:

String.format("(t%d, q%d) %s", transactionID,queryID, message)


6、正則表示式

正則表示式是一個強大而靈活的文字處理工具。它提供可一種完全通用的方式,能夠解決各種字串處理相關的問題:匹配、選擇、編輯和驗證。

正則可以切分,替換,判斷字串,通過設定的regex規則。 

基礎

在Java中,\\的意思是“我要插入一個正則表示式的反斜線,所以其後字元含有特殊意義”

\\d 表示一位數字

\\W 表示非單詞字元

\\w 表示單詞字元

\\\\ 表示普通反斜線

\n 換行符

\t 製表符

+ 一個或多個前一位的表示式

| 或者

? 可能有

\\+ 正號(加號在正則表示式中有特殊的意義,必須用\\將其轉譯為一個普通字元)


如下所示:

public class IntegerMatch {
public static void main(String[] args) {
System.out.println("-1234".matches("-?\\d+"));  //可能以負號開頭且後面跟著數字們
System.out.println("5678".matches("-?\\d+"));   
System.out.println("+911".matches("-?\\d+"));
System.out.println("+911".matches("(-|\\+)?\\d+"));  //可能以正負號開頭且後面跟著數字們
}
} /* Output:
true
true
false
true


split()方法目標是“將字串從正則表示式匹配的地方切開”,

如下:

//: strings/Splitting.java
import java.util.*;
public class Splitting {
public static String knights =
"Then, when you have found the shrubbery, you must " +
"cut down the mightiest tree in the forest... " +
"with... a herring!";
public static void split(String regex) {
System.out.println(
Arrays.toString(knights.split(regex)));
}
public static void main(String[] args) {
split(" "); // 從有空格處分開 
split("\\W+"); // 從有非單詞字元處分開
split("n\\W+"); // 從“字元n後面跟了非單詞字元”處分開
}
} /* Output:
[Then,, when, you, have, found, the, shrubbery,, you, must, cut, down,
the, mightiest, tree, in, the, forest..., with..., a, herring!]
[Then, when, you, have, found, the, shrubbery, you, must, cut, down,
the, mightiest, tree, in, the, forest, with, a, herring]
[The, whe, you have found the shrubbery, you must cut dow, the mightiest
tree i, the forest... with... a herring!]


String.split()還有個過載版本,允許限制字串分隔次數。

String類自帶“替換”正則表示式,如下所示:

import static net.mindview.util.Print.*;
public class Replacing {
static String s = Splitting.knights;
public static void main(String[] args) {

print(s.replaceFirst("f\\w+", "located"));  //以f開頭的單詞換成Located
print(s.replaceAll("shrubbery|tree|herring","banana"));  //這幾個單詞的位置換成banana
}
} /* Output:
Then, when you have located the shrubbery, you must cut down the
mightiest tree in the forest... with... a herring!
Then, when you have found the banana, you must cut down the mightiest
banana in the forest... with... a banana!


建立正則表示式


B字元B
\xhh十六進位制值為oxhh的字元
\uhhhh十六進位制表示為0xhhhh的Unicode字元
\t製表符
\n換行符
\r回車符
\f轉頁符
\e轉義符


字元類:

任意字元
[abc] 包含a、b、c的任何字元
[^abc] 除了a、b、c的任何字元
[a-zA-Z] 從a-z或者A-Z的所有字元
[abc[hij]] 包含a、b、c、h、i、j的所有字元
[a-z&&[hij]] 包含h、i、j(交集)
\s 空白符(空格、tab、換行、換頁、回車)
\S 非空白符
\d 數字[0-9]
\D 非數字[^o-9]
\w 詞字元[a-zA-Z_0-9]
\W 非詞字元[^\w]


邏輯操作符:

XY X後面有Y
X|Y X或Y
(X) 捕獲組。可以在表示式中用\i引用第i個捕獲組


邊界匹配符: ^一行的開始
一行的結束
\b 單詞的邊界
\B 非單詞的邊界
\G 前一個匹配的結束


量詞

量詞描述一個模式吸收輸入文字的方式:

貪婪型:

貪婪表示式會為所有可能的模式發現儘可能多的匹配。


勉強型:

用問號來指定,這個詞量匹配滿足模式所需的最少字元數。


佔有型:

Java特有。當正則表示式被用於字串時,它會產生相當多的狀態,以便在匹配失敗時可以回溯。而“佔有的”詞量並不儲存這些中間狀態,因此它們可以防止回溯。

貪婪型       勉強型                   佔有型              如何匹配

X?X?? X?+一個或0個X
X* X*?x*+0個或者多個X
x+ x+? X++一個或者多個X
X{n} X{n}? X{n}+恰好n次X
X{n,}  X{n,}? X{n,}+至少n次X
X{n,m}    X{n,m}?X{n,m}+X至少n次,且不超過m次


介面CharSequence從CharBuffer、String、StringBuffer、StringBuilder類中抽象出字元序列的一般化定義:

interface CharSequence {
charAt(int i);
length();
subSequence(int start,| int end);
toString();
}

這些類都實現了該介面。


Parttern和Matcher

Parttern物件表示編譯後的正則表示式。

1、匯入java.util.regex包

2、用static Parttern.compile()方法來編譯正則表示式。

3、使用已編譯的Parttern物件上的matcher()方法,加上一個輸入字串,從而共同構造一個Matcher物件。它有很多功能可用。


如下:

{Args: abcabcabcdefabc "abc+" "(abc)+" "(abc){2,}" }
import java.util.regex.*;
import static net.mindview.util.Print.*;
public class TestRegularExpression {
public static void main(String[] args) {
if(args.length < 2) {
print("Usage:\njava TestRegularExpression " +
"characterSequence regularExpression+");
System.exit(0);
}
print("Input: \"" + args[0] + "\"");
for(String arg : args) {
print("Regular expression: \"" + arg + "\"");
Pattern p = Pattern.compile(arg);
Matcher m = p.matcher(args[0]);
while(m.find()) {

print("Match \"" + m.group() + "\" at positions " +
m.start() + "-" + (m.end() - 1));
}
}
}
} /* Output:
Input: "abcabcabcdefabc"
Regular expression: "abcabcabcdefabc"
Match "abcabcabcdefabc" at positions 0-14
Regular expression: "abc+"
Match "abc" at positions 0-2
Match "abc" at positions 3-5
Match "abc" at positions 6-8
Match "abc" at positions 12-14
Regular expression: "(abc)+"
Match "abcabcabc" at positions 0-8
Match "abc" at positions 12-14
Regular expression: "(abc){2,}"
Match "abcabcabc" at positions 0-8


使用Matcher上的方法,我們能夠判斷各種不同型別的匹配是否成功:

boolean matches()判斷整個輸入字串是否匹配正則表示式,只有整個輸入都匹配正則表示式時才會true
boolean lookingAt()  判斷字串的開始部分是否能匹配模式,只有輸入的第一部分匹配才會true
boolean find()  在CharSequence中按照正則表示式查詢多個匹配
boolean find(int start) 在指定位置查詢多個匹配


如下所示: import java.util.regex.*;
import static net.mindview.util.Print.*;
public class Finding {
public static void main(String[] args) {
Matcher m = Pattern.compile("\\w+")    //模式\\w+將字串劃分為單詞
.matcher("Evening is full of the linnet’s wings");
while(m.find())
printnb(m.group() + " ");
print();
int i = 0;
while(m.find(i)) {
printnb(m.group() + " ");
i++;
}
}
} /* Output:
Evening is full of the linnet s wings
Evening vening ening ning ing ng g is is s full full ull ll l of of f
the the he e linnet linnet innet nnet net et t s s wings wings ings ngs
gs s


組是用括號劃分的正則表示式,組號為0表示整個正則表示式,組號為1表示第一隊括號()括起的組,依次類推。
Matcher物件提供了一系列方法,

public int groupCount() 返回該匹配器的模式中的分組數目,不包括第0組

public String group() 返回前一次匹配操作的第0組

public String group(int i) 返回前一次匹配操作指定的組號,如果匹配成功,但是指定的組沒有匹配輸入字串的任何部分,則將返回null。

public int start(int group) 返回前一次匹配操作中尋找到的組的起始索引

public int end(int group) 返回前一次匹配操作中尋找到的組的最後一個字元索引+1的值


Pattern標記

Pattern類的compile()方法還有另一個版本,它接受一個標記引數,以調整匹配的行為。

Pattern Pattern.compile(String regex, int flag)

flag來自Pattern類中的常量,常用的有Pattern.CASE_INSENSITIVE,Pattern.MULTILINE, 以及 Pattern.COMMENTS

split()

spilit()方法將輸入字元斷開成字串物件陣列,斷開邊界由正則表示式確定。預設情況下是遇到空格切分的。

String[] split(CharSequence input)  //通過正則表示式分隔input
String[] split(CharSequence input, int limit)   //通過正則表示式分隔input,分隔成limit個

如下所示:

import java.util.regex.*;
import java.util.*;
import static net.mindview.util.Print.*;
public class SplitDemo {
public static void main(String[] args) {
String input =
"This!!unusual use!!of exclamation!!points";
print(Arrays.toString(
Pattern.compile("!!").split(input)));
// Only do the first three:
print(Arrays.toString(
Pattern.compile("!!").split(input, 3)));
}
} /* Output:
[This, unusual use, of exclamation, points]
[This, unusual use, of exclamation!!points]


替換操作

replaceFirst(String replacement) 用於替換第一個匹配成功的部分為字串replacement
replaceAll(String replacement) 將匹配成功的所有部分替換為字串replacement
appendReplacement(StringBuffer sbuf,String replacement)執行漸進式替換
appendTail(StringBuffer sbuf,String repla