String StringBuilder StringBuffer的使用
一、String类:
1.String对象是不可变的:
String对象是不可变的。查看JDK文档就可以发现,String类中每一个看起来会修改String值的方法,实际上都是重新创建一个全新的String对象,以包含修改后的字符串内容,而最初的String对象则丝毫没有改变。
public class Test {运行结果:
public static void main(String[] args) {
String s0= "hello";
System.out.println("s0 之前的值 : "+s0); //第一次打印
String s1 = s0.toUpperCase(); //将s0包含的字符串转成大写
System.out.println("s0在toUpperCase 之后的值 : " + s0); //s0的值没有变化
System.out.println("s1的值 : " + s1);
System.out.println("比较s1与s0的引用 如果结果为false 则表示二者不是同一个引用,也就是重新生成了对象。");
System.out.println(s1 == s0);
}
}
s0 之前的值 : hello
s0在toUpperCase 之后的值 : hello
s1的值 : HELLO
比较s1与s0的引用 如果结果为false 则表示二者不是同一个引用,也就是重新生成了 对象。
false
2.当需要经常改变字符串内容时,应该避免使用String类:
因为String对象是不可变得,因而在改变String对象的内容时,都需要借助于StringBuilder类和它的append()方法,因而当更改 一次String对象的内容,会产生一个String对象和一个StringBuilder对象。下面是借助于javap工具查看编译后的字节码:
public class Test {利用JDK自带的javap -c Test 反汇编生成的字节码( 只截取对本问题分析有用的部分 ):
public static void main(String[] args) {
String s0 = "booth";
s0 = s0 + ".sun";
System.out.println(s0);
}
}
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String booth
2: astore_1
3: new #3 // class java/lang/StringBuilder</span>
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V</span>
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6 // String .sun
16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;</span>
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_1
23: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_1
27: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: return
}
注意代码段中红色部分,从这部分可以看出在改变字符串内容时是需要额外产生一个StringBuilder对象的。在加上重新生成的String对象(上面javap生成的字节码中第19行,调用了StringBuilder的toString方法,重新生成一个String对象),这样每次改变字符串内容时,隐含的将产生两个额外对象。这样如果这个改变字符串的操作方法一个for循环,那么将会产生很多的无用对象,这将增加GC的工作量。
3.编译器对于String的优化:
String对象的字符串”+”操作是要借助于StringBuilder操作的,因而这个时候String对象的运行速度和效率是低于StringBuilder 的,但特别的是以下的字符串拼接操作中,Stirng效率要远远比StringBuffer快的很多:
String s1 = “This is only a ” + “simple” + “test”;
StringBuilder sb = new StringBuilder(“This is only a ”).append(“ simple ”).append(“ test ”);
这里是因为javac在编译的时候,会进行相应的优化操作,在编译器眼里:
String s1 = “This is only a ” + “ simple ” + “ test ”;其实就是;
String s1 = “This is only a simple test”;
因而这个变量在编译的时候就已经构建好了,运行时,肯定就速度很快了。但是如果你的字符串是来自于另外的Stirng对象的话,则又需要借助于StringBuilder。如;
String s1 = “This is only a ”;
String s2 = “simple”;
String s3 = “test”;
s1 = s1 + s2 + s3;
这时候 JVM 会规规矩矩的按照原来的方式去做, S1 对象的生成速度就不像刚才那么快了。
二、StirngBuffer与StringBuilder类
1.简介(复制于JDK API):
Java.lang.StringBuffer 线程安全的可变字符序列。类似于 String 的字符串缓冲区,但不能修改。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
每个字符串缓冲区都有一定的容量。只要字符串缓冲区所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区数组。如果内部缓冲区溢出,则此容量自动增大。从 JDK 5.0 开始,为该类增添了一个单个线程使用的等价类,即 StringBuilder 。与该类相比,通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。
但是如果将 StringBuilder 的实例用于多个线程是不安全的。需要这样的同步,则建议使用 StringBuffer 。
2.修改操作:
对StringBuffer对象内容进行修改时,每次结果都是对StringBuffer对象本身进行操作,而不是生成一个新的对象,在改变对象的引用。
源代码:
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("booth");
StringBuffer sb2 = sb.append("sun").append("jxau");
System.out.println(sb == sb2);
}
输出结果 : true<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
三、 总结:
在大部分情况下:
StringBuilder > StringBuffer > String
既然有这样的推导结果了,我们做个测试验证一下:
测试代码如下:
package com.booth.sun.string;运行结果:
import java.io.StringBufferInputStream;
public class StringTest {
/** Creates a new instance of Splitting */
final static int ttime = 10000;// 测试循环次数
public StringTest() {
}
public void test(String s) {
long begin = System.currentTimeMillis();
for (int i = 0; i < ttime; i++) {
s += "add";
}
long over = System.currentTimeMillis();
System.out.println(" 操作 " + s.getClass().getName() + " 类型使用的时间为: "
+ (over - begin) + " 毫秒 ");
}
public void test(StringBuffer s) {
long begin = System.currentTimeMillis();
for (int i = 0; i < ttime; i++) {
s.append("add");
}
long over = System.currentTimeMillis();
System.out.println(" 操作 " + s.getClass().getName() + " 类型使用的时间为: "
+ (over - begin) + " 毫秒 ");
}
public void test(StringBuilder s) {
long begin = System.currentTimeMillis();
for (int i = 0; i < ttime; i++) {
s.append("add");
}
long over = System.currentTimeMillis();
System.out.println(" 操作 " + s.getClass().getName() + " 类型使用的时间为: "
+ (over - begin) + " 毫秒 ");
}
// 对 String 直接进行字符串拼接的测试
public void test2() {
String s2 = "abadf";
long begin = System.currentTimeMillis();
for (int i = 0; i < ttime; i++) {
String s = s2 + s2 + s2;
}
long over = System.currentTimeMillis();
System.out.println(" 操作字符串对象引用相加类型使用的时间为: " + (over - begin) + " 毫秒 ");
}
public void test3() {
long begin = System.currentTimeMillis();
for (int i = 0; i < ttime; i++) {
String s = "abadf" + "abadf" + "abadf";
}
long over = System.currentTimeMillis();
System.out.println(" 操作字符串相加使用的时间为: " + (over - begin) + " 毫秒 ");
}
public static void main(String[] args) {
String s1 = "abc";
StringBuffer sb1 = new StringBuffer("abc"); // 线程安全的 速度慢点
StringBuilder sb2 = new StringBuilder("abc"); // 线程不安全的 速度快点
StringTest t = new StringTest();
t.test(s1);
t.test(sb1);
t.test(sb2);
t.test2();
t.test3();
}
}
操作 java.lang.String 类型使用的时间为: 238 毫秒其实我这里测试并不是很公平,因为都放在了一起以先后顺序进行,测试方法中间没有考虑到JVM的GC收集前面产生的无引用对象垃圾而对执行过程的中断时间。
操作 java.lang.StringBuffer 类型使用的时间为: 1 毫秒
操作 java.lang.StringBuilder 类型使用的时间为: 1 毫秒
操作字符串对象引用相加类型使用的时间为: 3 毫秒
操作字符串相加使用的时间为: 0 毫秒
参考资料: 是 String , StringBuffer 还是 StringBuilder