本文包含以下内容:
- String不可变
- 字符串连接
- StringBuilder
- StringBuffer
- 使用正则表达式
- 可能出现的坑
String不可变
Java中字符串使用String对象表示,它是不可变的,调用String对象的任何方法都不会改变字符串的内容,但是它会返回一个全新的字符串来表示方法操作的结果。
String original = "hello world!";
String newString = original.toUpperCase();
System.out.println(original);
System.out.println(newString);
/*程序输出
hello world!
HELLO WORLD!
*/
上例中,调用toUpperCase方法没有改变原始字符串的内容而是生成了一个新的字符串。
字符串连接
在实际开发中字符串的连接操作使用很频繁,很多编程语言中字符串连接使用“+”操作符,Java也支持通过“+”操作符连接字符串。
String prefix = "hello";
String postfix = "world!";
String whole = prefix + " " + postfix;
System.out.println(whole);
/*程序输出
hello world!
*/
由于字符串的不可变性,每个连接操作符“+”的调用会产生一个新的对象,如果连接的字符串过多则会产生很多新的中间对象,而我们只使用最终的对象。上例中prefix + " "会产生一个新的中间对象,这个对象不是我们需要的,我们需要的是连接最终生成的whole对象。大量字符串连接会产生一堆需要垃圾回收的中间对象,所以它会影响应用程序的性能。
StringBuilder
StringBuilder用来辅助生成String,可以把StringBuilder理解为可变的String,可变意味着调用StringBuilder对象的方法会改变字符串内容,对StringBuilder的操作不会生成中间对象。字符串的连接操作可以使用StringBuilder的append方法来代替:
StringBuilder sb = new StringBuilder();
sb.append("hello");
sb.append(" ");
sb.append("world!");
System.out.println(sb.toString());
/*程序输出
hello world!
*/
相比字符串连接操作,StringBuilder性能要好一些,因为它不会产生中间对象给垃圾回收器制造负担。
鉴于字符串连接的低效性,编译器对其进行了优化,优化方式正是用StringBuilder的append方法代替字符串连接操作符“+”。但是优化毕竟有限的,很多优化还得使用者来做,以下是编译器优化的一个示例:
//编译器优化以前的代码
public String example(String[] fields)
{
String result = "";
for (String str: fields) {
result += str;
}
return result;
}
//编译器优化后的示例代码
//以下代码只是为了方便理解编译器优化的示例代码
public String example(String[] fields)
{
String result = "";
for (String str: fields) {
StringBuilder sb = new StringBuilder(result);
sb.append(str);
result = sb.toString();
}
return result;
}
可以看到在循环里面使用字符连接操作符时,每次循环都会创建一个StringBuilder对象,这显然不是最优解。我们可以直接使用StringBuilder做优化,示例如下:
//主动优化的代码
public String example(String[] fields)
{
StringBuilder sb = new StringBuilder();
for (String str: fields) {
sb.append(str);
}
return sb.toString();
}
在没有循环时编译器的优化是值得信赖的,可以直接使用字符串连接操作符“+”,当涉及到循环时编译器优化可能不是最好的,这时可以考虑使用StringBuilder来优化程序。
StringBuffer
StringBuffer可以理解为线程安全版本的StringBuilder,线程安全的StringBuffer相比StringBuilder有些额外的性能开销。 同一个StringBuffer对象可以放在不同的线程中使用,而StringBuilder则不能。
使用正则表达式
正则表达式用来描述一组具有相同特征的字符串,使用正则表达式可以很方便的进行查找和替换操作。String的下列方法同时支持普通字符串和正则表达式:
- matches
- replaceAll
- replaceFirst
- split
matches方法用来判断整个字符串是否和正则表达式匹配。replaceAll用来替换所有正则表达式匹配到的子字符串,replaceFirst则只替换匹配到第一个子字符串。split用来把字符串从正则表达匹配到的位置拆分开,返回拆分后的字符串数组。关于正则表达式的进阶用法,将在后面的文章中讲解。
可能出现的坑
每个Java对象都有一个toString方法,该方法用来返回一个对象的描述信息,toString方法默认返回对象的地址信息,可以通过覆写来返回定制的内容。如果在toString方法里面使用this连接字符串会出现堆栈溢出错误,示例如下:
public class Test {
public String toString() {
return "Test " + this;
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.toString());
}
}
/*输出:
抛出java.lang.StackOverflowError
*/
上面的代码中,toString方法里面的语句把this对象和字符串进行连接操作,编译器会把this转换为字符串,怎么转换呢?答案是调用this.toString()方法,那么问题就出来了,toString方法里面调用toString方法,程序陷入了无限递归调用直到堆栈溢出。在toString方法里面使用this可能是为了获取对象的地址信息,可以通过super.toString()方法来获取地址信息,示例如下:
public class Test {
public String toString() {
return "Test " + super.toString();
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.toString());
}
}
/*输出:
Test com.shaoshuidashi.Test@154617c
*/
最后
字符串是一种比较常用的工具,在实际使用时需要注意效率问题,当一个字符串是通过一个循环体构造而来时,我们脑海中应该马上想到StringBuilder。