String和StringBuilder和StringBuffer三兄弟

  • 前言
  • 分析
    • 内部实现
    • 运行速度StringBuilderStringBufferString
    • 线程安全
  • 后言
  • 总结

前言

好久之前在写文件上传的时候使用了这样的一段代码

/**
* @param rootUrlStr:保存的路径的文件夹路径 假设就是 D:\save
* @param fileUriStr:需要保存的文件的具体路径
* @about 这是一段精简的代码
* @return 存储文件地址
*/
public String uploadUri1(String rootUrlStr,String fileUriStr) {
    //下面我将故意使用很复杂的拼接
    //获得文件名,故意不使用UUID.randomUUID()
    String fileNameStr=fileUriStr.substring(fileUriStr.lastIndexOf("\\"));
    String fileRealUriStr=rootUrlStr+"\\";
    fileRealUriStr+="uploadFile\\";
    fileRealUriStr+=fileNameStr;//把根路径和文件名拼接在一起
    return fileRealUriStr;
}

这种写法很明显的一点就是会生成很多的中间对象。
于是我后来改成了这样

public String uploadUri2(String rootUrlStr,String fileUriStr) {
    //下面我将故意使用很复杂的拼接
    //获得文件名,故意不使用UUID.randomUUID()
    StringBuffer fileRealUriStr=new StringBuffer(rootUrlStr);
    String fileNameStr=fileUriStr.substring(fileUriStr.lastIndexOf("\\"));
    fileRealUriStr.append("uploadFile\\").append(fileNameStr);
    return fileRealUriStr.toString();
}

使用StringBuffer去避免生成太多临时字符串。
当然有更好的方法,那就是使用Paths

public String uploadUri3(String rootUrlStr,String fileUriStr) {
    //下面使用Path
    //获得文件名,故意不使用UUID.randomUUID()
    String fileNameStr=fileUriStr.substring(fileUriStr.lastIndexOf("\\"));
    Path path=Paths.get(rootUrlStr,"uploadFile",fileNameStr);
    return path.toString();
}

Paths会自动补全”\”,使得代码比较直观好看。他不是今天的主角,所以我就不展开讲了。

//测试代码
public static void main(String[] args) {
    String string1=new MyText().uploadUri1("D:save","I:\\JAVA\\java.txt");
    String string2=new MyText().uploadUri2("D:save","I:\\JAVA\\java.txt");
    String string3=new MyText().uploadUri3("D:save","I:\\JAVA\\java.txt");
    System.out.println(string1);
    System.out.println(string2);
    System.out.println(string3);
}

输出结果

D:save\uploadFile\java.txt
D:save\uploadFile\java.txt
D:save\uploadFile\java.txt

以上是可以跳过不看的内容


分析

内部实现

  • String:

    String类使用字符串数组保存字符串,因为使用final修饰符,所以可知道String对象是不可变的。所谓的不可变其实是指每次修改都不是在原字符上修改,而是新建了一个新的字符数组,而且如果这个对象没有被引用,那这个对象就是没有用的。

  • StringBuffer:

    继承了AbstractStringBuilder,而且和String不一样的是
    String使用的是数组声明为 private final char value[];
    StringBuffer的声明是 private transient char[] toStringCache;
    transient :临时的
    当StringBuffer进行修改时,(比如说删除、更新字符)是在原来的实例对象进行修改的。但是如果是拼接操作时,分成两种情况,1、空间足够,直接拼接;2、空间不够,新建了一个字符串数组,再搬家过去。

  • StringBuilder:

    StringBuffer和StringBuffer都是继承了AbstractStringBuilder,区别只是方法签名上是否有synchronized。因此,StringBuffer是线程安全的,而StringBuilder是线程不安全的。
    String和StringBuilder和StringBuffer三兄弟_第1张图片
    HashTable是线程安全的,很多方法都是synchronized方法,而HashMap不是线程安全的,但其在单线程程序中的性能比HashTable要高。StringBuffer和StringBuilder类的区别也是如此,他们的原理和操作基本相同,区别在于StringBufferd支持并发操作,线性安全的,适 合多线程中使用。StringBuilder不支持并发操作,线性不安全的,不适合多线程中使用。新引入的StringBuilder类不是线程安全的,但其在单线程中的性能比StringBuffer高。

运行速度(StringBuilder>StringBuffer>String)

  • String 每次都要新增临时字符串,开销大,很慢(GC工作压力大)
  • StringBuffer 建立线程安全容器,开销大,中等
  • StringBuilder 单线程推荐使用,线程不安全,快
    感觉直接这样说,你还是不相信,还是觉得使用String多好啊,敲起来还短,所以我就提供了一段代码,这段代码不是我原创的,但是写的很好,我就借来修改了一下,代码如下
package javaTest;


/**
 *@author CHEN
 *@time 2016年4月15日
 *@about 测试String StringBuffer StringBuilder的性能 
 */
public class StringBuilderTester {
    private static final String base = " base string. ";
    private static final int count = 200000;

    public static void stringTest() {
        long begin, end;
        begin = System.currentTimeMillis();
        String test = new String(base);
        for (int i = 0; i < count ; i++) {
            test = test + " add ";
        }
        end = System.currentTimeMillis();
        System.out.println((end - begin)
                + " millis has elapsed when used String. ");
    }

    public static void stringBufferTest() {
        long begin, end;
        begin = System.currentTimeMillis();
        StringBuffer test = new StringBuffer(base);
        for (int i = 0; i < count; i++) {
            test = test.append(" add ");
        }
        end = System.currentTimeMillis();
        System.out.println((end - begin)
                + " millis has elapsed when used StringBuffer. ");
    }

    public static void stringBuilderTest() {
        long begin, end;
        begin = System.currentTimeMillis();
        StringBuilder test = new StringBuilder(base);
        for (int i = 0; i < count; i++) {
            test = test.append(" add ");
        }
        end = System.currentTimeMillis();
        System.out.println((end - begin)
                + " millis has elapsed when used StringBuilder. ");
    }


    public static void main(String[] args) {
        stringTest();
        stringBufferTest();
        stringBuilderTest();
    }
}

运行结果:

113730 millis has elapsed when used String.
13 millis has elapsed when used StringBuffer.
9 millis has elapsed when used StringBuilder.

建议在使用的时候,把count的值乘以10,但stringTest中count缩小100倍,有利于比较StringBuffer和StringBuilder。

线程安全

  • String :String是不可变的,所以也就是线程安全的
  • StringBuffer:线程安全,
  • StringBuilder:线程不安全
package hello;

/**
 * @about 对StringBuffer StringBuilder String的线程测试
 * @author CHEN
 * @time 2016年4月15日
 */
public class Test {
    public static void main(String[] args) {
        StringBuffer sbf = new StringBuffer();
        StringBuilder sb = new StringBuilder();
        String s=new String();
        //10个线程
        for (int i = 0; i < 10; i++) {
            new Thread(new TestThread(sbf, sb, s)).start();
        }
    }
}

class TestThread implements Runnable {
    StringBuffer sbf;
    StringBuilder sb;
    String s;

    TestThread(StringBuffer sbf, StringBuilder sb,String s) {
        this.sb = sb;
        this.sbf = sbf;
        this.s=s;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sb.append("1");
            sbf.append("1");
            s+="1";
            System.out.println(sb.length() + "/" + sbf.length()+"/"+s.length());
        }
    }
}

运行的结果是:

sb很少次能达到1000次
sbf基本都达到1000次
而s 则是100次


后言

  • 如果阅读String的“+”的字节码,其实你就会发现,在底层,系统自动调用了StringBilder。
    例如下面的代码
public class Buffer {
     public static void main(String[] args) {
        String s1 = "aaaaa";
        String s2 = "bbbbb";
        String r = null;
        int i = 3694;
        r = s1 + i + s2; 

        for(int j=0;i<10;j++){
            r+="23124";
        }
     }
}

JVM将会翻译成
String和StringBuilder和StringBuffer三兄弟_第2张图片
偷偷的调用了StringBuilder,为什么呢?当然是因为快啊。但是别以为JVM帮你做了这部分工作,你就可以滥用String了。String转成StringBuilder每次都会建立很多的对象的,所以呢,作为一个好的码农,第一件事就是为JVM多考虑。

总结

就这样我们认识了String、StringBuffer、StringBuilder三兄弟。
大哥String,虽说顽固不变,但是通用性好,用途广泛,占用内存小,大众都喜欢使用它。可是呢,其实大哥String的工作经常是交给小弟StringBuilder做的。
二哥StringBuffer,比大哥通达,改变的时候就会改变。可是,别人让他办事,他每次就答应办一件,每次一件,所以比较可靠安全。
小弟StringBuilder,比较活泼,有时候同时办好几件事,就把事给办坏了。可是呢,小弟他的工作效率是最快的。

你可能感兴趣的:(Java)