1. 程式人生 > 實用技巧 >《Thinking In Java》筆記之十三章 字串

《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());
    }
}