String和StringBuffer和StringBuilder的常识性知识

时间:2022-06-16 04:01:40

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 {
public static void main(String[] args) {
String s0 = "booth";
s0 = s0 + ".sun";
System.out.println(s0);

}
}
利用JDK自带的javap -c Test 反汇编生成的字节码( 只截取对本问题分析有用的部分 ):

  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行,调用了StringBuildertoString方法,重新生成一个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 对象的生成速度就不像刚才那么快了。

二、StirngBufferStringBuilder

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 毫秒 
操作 java.lang.StringBuffer 类型使用的时间为: 1 毫秒
操作 java.lang.StringBuilder 类型使用的时间为: 1 毫秒
操作字符串对象引用相加类型使用的时间为: 3 毫秒
操作字符串相加使用的时间为: 0 毫秒
    其实我这里测试并不是很公平,因为都放在了一起以先后顺序进行,测试方法中间没有考虑到JVM的GC收集前面产生的无引用对象垃圾而对执行过程的中断时间。

参考资料: 是 String , StringBuffer 还是 StringBuilder