《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 + "
";
        //此处使用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]
", x, y);
        System.out.printf("Row 1: [%d %f]
", 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)
", 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
", "Item", "Qty", "Price");
        f.format("%-15s %5s %10s
","----", "---", "-----");
    }
    public void print(String name, int qty, double price) {
        f.format("%-15.15s %5d %10.2f
",name, qty, price);
        //%-15.15s,-表示左对齐,前15表示width,后15表示precision
        total += price;
    }
    public void printTotal() {
        f.format("%-15s %5s %10.2f
", "Tax", "", total * 0.06);
        f.format("%-15s %5s %10s
", "", "", "-----");
        f.format("%-15s %5s %10.2f
", "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("
");
        }
        result.append("
");
        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());
    }
}

原文地址:https://www.cnblogs.com/Glov/p/13512872.html