1.1. String API
String对象是不可变的,那些看似改变了对象的方法其实是返回新的 String 对象,有一些方法使用时是要注意的。
A、 substring、 subSequence 方法。
String底层是使用字符数组来存储的, substring 、 subSequence 方法返回新的String 或 CharSequence ,共享底层的字节数组,因为它是不可变的,这能提升性能,但也能导致问题。
如果原字符串是一个很大的临时字符串,但只需保留一小块子字符串时,使用sub* 就会导致内存的浪费。这篇博客描述了问题的情况:
http://www.ibm.com/developerworks/cn/java/j-lo-optmizestring/
B、 实现对象的toString 方法
这点是任何类实现toString 方法时都必须注意的。
public class InfinitRecursion { public String toString() { return "address:" + this; } public static void main(String[] args) { new InfinitRecursion().toString(); } }
InfinitRecursion 类的 toString方法是希望输出对象的内存地址,但调用这个方法将导致 StackOverflowError ,因为使用"+" 连接时会,编译器看到 this 不是一个 String 对象,会尝试将 this 转换成一个 String ,而这个转换就是通过调用 toString 方法实现的,这就无意发生了递归调用,导致栈溢出异常。要打印对象的内存地址,应该使用 Object.toString() 。
1.2. 字符串拼接
字符串拼接一般有:+ 、 StringBuffer 、 StringBuilder 三种形式。
+拼接示例:
public static void main(String[] args) { String a = "123" + "456" + "a"; String ab = a + "b" + "+-*/"; String abc = ab + "c"; }
使用 “ javap -c 类名 ” 查看编译后的字节码大致是这样的:
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String 123456a
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<
init>":()V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6 // String b
16: invokevirtual #5 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #7 // String +-*/
21: invokevirtual #5 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
27: astore_2
28: new #3 // class java/lang/StringBuilder
31: dup
32: invokespecial #4 // Method java/lang/StringBuilder."<
init>":()V
35: aload_2
36: invokevirtual #5 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
39: ldc #9 // String c
41: invokevirtual #5 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
44: invokevirtual #8 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
47: astore_3
48: return
从字节码可以看到:
1、 “0: ldc #2 // String 123456a ” 说明如果 + 只涉及常量,编译器会把常量合并了;
2、 从第14 、 16 、 19 、 21 字节码指令说明,如果 + 操作中涉及变量,编译器不会合并常量;
3、 第3 到 24 条字节码指令说明,编译器将 + 操作转换为 StringBuidler.append 操作,在执行赋值语句时会调用 StringBuilder.toString 方法来生成完整的字符串。
4、 从3 到 24 条指令 与 28 到 44 条指令说明,对于每条使用 + 拼接进行赋值的语句都会生成一个 StringBuilder 。
根据上面的结果:
1、 尽量不要使用+ 拼接,就算要用,也应该写成一条语句。
2、 绝对不应该在循环语句里使用+ 拼接,这会导致生成大量的 StringBuilder 。
StringBuilder和 StringBuffer 都使用数组作为底层存储结构,通过预分配数组空间,避免在每次 append 时扩容,有助于提升性能。
StringBuffer的所有方法都是线程安全的,在不需要线程安全的情况下,这会带来额外的性能开销。
StringBuilder是非线程安全版的 StringBuffer 。