String和StringBuilder和StringBuffer三兄弟

来源:互联网 发布:python 面部表情识别 编辑:程序博客网 时间:2024/05/17 04:36

String和StringBuilder和StringBuffer三兄弟


前言

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

/*** @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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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

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();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

使用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();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

输出结果

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是线程不安全的。
    StringBuffer和StringBuilder类结构图
    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();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

运行结果:

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());        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

运行的结果是:

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";        }     }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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

总结

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


 
 
0 0
原创粉丝点击