《Thinking In Java》筆記之十三章 字串
#Thinking In Java# Chapter13 String
不可變String
String類中看起來會修改String的方法,實際上均為建立了一個全新的物件,而最初的物件絲毫未動,對方法傳遞字串,實際傳遞的是引用的一個拷貝,而該引用所指的物件一直待在原物理位置上,從未動過。
過載“+”與String builder
package strings; public class WhitherStringBuilder { public String implicit(String[] fields) { String result = ""; for (int i = 0; i < fields.length; i++) { result += fields[i]; } return result; } public String explicit(String[] fields) { StringBuilder result = new StringBuilder(); for (int i = 0; i < fields.length; i++) { result.append(fields[i]); } return result.toString(); } }
方法一使用了多個String物件,方法二使用了StringBuilder,執行javap -c WitherStringBuilder檢視位元組碼。可知對於implicit方法,在每一次迴圈內部都生成了一個新的StringBuilder物件。而explicit方法迴圈部分程式碼更簡短,只生成一個StringBuilder物件,顯示地建立還可以預先為StringBuilder指定大小,如果已經知道最後的字串大小大概有多長,可以預先指定大小避免多次重新分配快取。
因此如果為一個類編寫toString()方法時,如果字串操作比較簡單,可以信賴編譯器。但如果藥在toString方法中使用迴圈,最好自己建立一個StringBuilder物件,用它來構造最終的結果。
參考示例
package strings; import java.util.Random; public class UsingStringBuilder { public static Random rand = new Random(47); public String toString() { StringBuilder result = new StringBuilder("["); for (int i = 0; i < 25; i++) { result.append(rand.nextInt(100)); result.append(","); } result.delete(result.length() - 1, result.length()); result.append("]"); return result.toString(); } public static void main(String[] args) { UsingStringBuilder usb = new UsingStringBuilder(); System.out.println(usb); } } /* [58,55,93,61,61,29,68,0,22,7,88,28,51,89,9,78,98,61,20,58,16,40,11,22,4] */
StringBuilder提供了豐富全面的方法,包括insert(),replace(),substring()甚至reverse(),但最常用的還是append()和toString(),還有delete()方法,StringBuilder是Java SE5引入的,在這之前用的是StringBuffer。後者是執行緒安全,開銷大,所以在Java SE 5/6,字串操作應該更快。
無意識的遞迴之toString()方法
如果你希望 toString()方法打印出物件的記憶體地址,也許你會考慮使用this關鍵字
package strings;
import java.util.ArrayList;
import java.util.List;
public class InfiniteRecursion {
@Override
public String toString() {
return "InfiniteRecursion address: " + this + "\n";
//此處使用this造成的遞迴,你看出來了嗎?( ̄▽ ̄)"
}
public static void main(String[] args) {
List<InfiniteRecursion> v = new ArrayList<InfiniteRecursion>();
for (int i = 0; i < 10; i++) {
v.add(new InfiniteRecursion());
}
System.out.println(v);
}
}
然而在列印時卻會一串非常長的異常,這裡發生了自動型別轉換,由InfiniteRecursion型別轉換成String型別。因為編譯器看到一個String物件後接著一個“+”,而後面的物件並不是String,再次發生型別轉換,呼叫this的toString()方法,發生遞迴呼叫。
如果真的想要打印出地址,應該呼叫Object.toString(),所以應該呼叫super.toString()方法,而不是thos
String上的操作
以下是String物件具備的一些基本方法。過載的方法歸納在同一行中:
構造器 | 引數,過載版本 | 應用 |
---|---|---|
length() | String中字元個數 | |
charAt() | Int索引 | 取得String該索引位置上的char |
getChars(),getBytes() | 要複製部分起點和終點的索引,要複製的目標陣列,目標陣列的起始索引 | 複製char或byte到一個目標數組裡 |
toCharArray() | 生成一個char[],包含String的所有字元 | |
equals(), equalsIgnoreCase() | 與之進行比較的String | 比較兩個String的內容是否相同 |
compareTo() | 與之進行比較的String | 按詞典順序比較String的內容,比較結果為正數,負數,零 |
contains() | 要搜尋的CharSequence | 如果String物件包含引數的內容,則返回true |
contentEquals() | 與之比較的CharSequence或StringBuffer | 如果該String與引數的內容完全一致 |
equalsIgnoreCase() | 與之進行比較的String | 忽略大小寫,如果兩個String內容相同,返回true |
regionMatcher() | 該索引的偏移量,另一個String及其索引偏移量,要比較的長度。過載版本增加了“忽略大小寫功能” | 返回boolean結果,以表明所比較區域是否相等 |
startsWith | 可能的起始String。過載版本在引數中增加了偏移量 | 返回boolean結果,以表明該String是否以此引數開始 |
endsWith | 該String可能的字尾String | 返回boolean結果,以表明此引數是否是該字串的字尾 |
indexOf(),lastIndexOf() | 過載版本包括:char, char與起始索引,String, String與起始索引 | 如果該String不包含該引數,就返回-1;否則返回該引數在String中。LastIndexOf()是從後往前搜尋 |
substring()(subSequence()) | 過載版本:起始索引;起始索引+終點座標 | 返回一個新的String以包含指定的子字串 |
concat() | 要連線的String | 返回一個新的String物件,內容為原始String連線上引數String |
replace() | 要替換掉的字元,用來進行替換的新字元。也可以用一個CharSequence來替換另一個CharSequence | 返回替換字元後的新String物件,如果沒有替換髮生,則返回原來的String物件 |
toLowerCase() toUpperCase() | 將字元的大小寫改變後,返回一個新String物件。如果沒有發生改變,則返回原始的String物件 | |
trim() | 將String兩端的空白字元刪除後,返回一個新的String物件。如果沒有發生改變,則返回原始的String物件 | |
valueOf() | Object; char[];char[], 偏移量,與字元個數;boolean; char; int; long; float; double | 將基本資料型態轉換成 String |
intern() | 為每個唯一的字元序列生成一個且僅生成一個String引用 |
格式化輸出
System.out.format()
package strings;
public class SimpleFormat {
public static void main(String[] args) {
int x = 5;
double y = 3.1415926;
System.out.println("Row 1 : [" + x + "" + y + "]");
//new ways
System.out.format("Row 1: [%d %f]\n", x, y);
System.out.printf("Row 1: [%d %f]\n", x, y);
}
}
/*
Row 1 : 53.1415926]
Row 1: [5 3.141593]
Row 1: [5 3.141593]
*/
Formatter類
在Java中,所有新的格式化功能java.util.Formatter。可以將Formatter看作一個翻譯器,將格式化字串與資料翻譯成需要的結果,當你建立一個Formatter物件的時候,需要向構造器傳遞一些資訊,告訴他最終結果向哪裡輸出:
package strings;
import javax.swing.plaf.synth.SynthEditorPaneUI;
import java.io.PrintStream;
import java.util.Formatter;
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 tom = new Turtle("Tom", new Formatter(System.out));
Turtle jerry = new Turtle("Jerry", new Formatter(outAlias));
tom.move(0, 0);
jerry.move(4, 8);
tom.move(3, 4);
jerry.move(2, 5);
tom.move(3, 3);;
jerry.move(3, 3);
}
}
/*
tommy The turtle is at (0 , 0)
Turtle The turtle is at (4 , 8)
tommy The turtle is at (3 , 4)
Turtle The turtle is at (2 , 5)
tommy The turtle is at (3 , 3)
Turtle The turtle is at (3 , 3)
*/
所有的tom都會被輸入得到System.out中,而所有的jerry都會輸出到System.out的別名上。Formatter的構造器過載後可以接收多種輸出目的地,包括PrintStream(),OutputStream和File
格式化說明符
抽象語法:%[argument_index$] [flags] [width] [.precision] conversion
預設情況下,資料是右對齊,可以使用“-”左對齊
width:指明最大尺寸,可以應用於各種型別的資料轉換,並且行為方式都一樣
precision: 不是所有型別的資料都能使用precision,而且應用於不同的型別資料含義不同,在將precision應用於String時,表示String可輸出字元的最大數量(注意體會與width的區別,width強調的是尺寸),而precision應用於浮點數表示小數部分要顯示出來的位數。小數位數過多舍入,太少尾部補0。precision不能應用於整數,因為整數沒有小數部分。
package strings;
import java.util.Formatter;
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");
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);
//%-15.15s,-表示左對齊,前15表示width,後15表示precision
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();
}
}
/*
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 |
b | boolean |
s | String |
f | 浮點值 |
e | 浮點數(科學計數) |
x | 整數(十六進位制) |
h | 雜湊碼(十六進位制) |
% | 字元“%” |
荔枝:
Formatter f = new Formatter();
int i = 0;
f.format("b : %b", i);
/*
b : true
*/
b轉換:對於boolean基本型別或者Boolean物件,其轉換型別永遠是true或false,對其他型別的引數,只要該引數不為null,那麼轉換的結果永遠為true,即使是數字0,這與C等其他語言中不一樣,需要小心。
便捷的String.format()
String.format()是一個static方法,它接受與Formatter.format()方法一樣的引數,但是,但是,但是,它返回一個String物件。當你只需format()方法一次的時候,String.format()使用起來非常方便。其實在String.format()內部它也建立了一個Formatter物件,然後將引數傳入Formatter。但不如使用便捷的String.format()方法並且使程式碼更具有可讀性。
package strings;
public class DatabaseException extends Exception {
public DatabaseException(int transactionID, int queryId, String message) {
super(String.format("(transactionID%d, queryId%d) %s", transactionID, queryId, message));
}
public static void main(String[] args) {
try {
throw new DatabaseException(3, 7, "write failed");
} catch (Exception e) {
System.out.println(e);
}
}
}
dump工具(十六進位制轉換)
package strings;
import java.io.*;
public class Hexadecimal {
public static String format(byte[] data) {
StringBuilder result = new StringBuilder();
int n = 0;
for (byte b : data) {
if (n % 16 == 0)
result.append(String.format("%05X: ", n)); // 佔用5個位置(16進製表示)
result.append(String.format("%02X ", b)); // 佔用2個位置(16進製表示)
n++;
if (n % 16 == 0)
result.append("\n");
}
result.append("\n");
return result.toString();
}
public static void main(String[] args) throws Exception {
if (args.length == 0)
System.out.println(format(BinaryFile.read("E:/ThinkingInJava/src/strings/Hexadecimal.java")));
else
System.out.println(format(BinaryFile.read(new File(args[0]))));
}
}
/*
00010: 0D 0A 0D 0A 69 6D 70 6F 72 74 20 6A 61 76 61 2E
00020: 69 6F 2E 2A 3B 0D 0A 0D 0A 70 75 62 6C 69 63 20
...
*/
package strings;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class BinaryFile {
public static byte[] read(File bFile) throws IOException {
BufferedInputStream bf = new BufferedInputStream(new FileInputStream(
bFile));
try {
byte[] data = new byte[bf.available()];
bf.read(data);
return data;
} finally {
bf.close();
}
}
public static byte[] read(String bFile) throws IOException {
return read(new File(bFile).getAbsoluteFile());
}
}