浅谈Java中String、StringBuffer、StringBuilder

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/YaboSun/article/details/81012554

参考:
https://www.programmergate.com/stringbuilder-vs-stringbuffer/
https://blog.csdn.net/rmn190/article/details/1492013
https://droidyue.com/blog/2014/12/21/string-literal-pool-in-java/

String 字符串常量 StringBuffer 字符串变量(线程安全)StringBuilder 字符串变量(非线程安全)

String

Strings are constant; their values cannot be changed after they are created.
这是JavaAPI中对于String的定义,就是说String是常量,一旦定义就无法改变。说到常量就不得不提到Java中的字符串常量池,通过String的俩种创建方法来进行了解

String俩种创建方式区别

Java中字符串对象创建有两种形式,一种为字面量形式,如String str = “abc”;,另一种就是使用new这种标准的构造对象的方法,如String str = new String(“abc”);
当Java中出现第一种字面量形式创建字符串时,JVM首先会在常量池中进行查找是否存在相同字符串对象内容的引用,如果存在,那么直接将新创建的对象指向该引用地址,如果不存在则创建新的引用对象,并把该引用对象放入常量池

public class Test {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abc";
        System.out.println(str1 == str2); // true
    }
}

通过以上代码可以看到结果为true 也就是说str1 str2 指向的是相同的地址。现在将代码改变:

public class Test {
    public static void main(String[] args) {
        String str1 = "abc";
        str1 = "xyz";
        String str2 = "abc";
        System.out.println(str1 == str2); // false
    }
}

当JVM看到"xyz",在字符串常量池创建新的String对象存储它,再把新建的String对象的引用返回给str1,所以这时候str1 指向的是xyz ,而abc 还是存在于字符串常量池中,其值并不能被改变,所以返回false
接下来继续将代码改为以下:

public class Test {
    public static void main(String[] args) {
        String str1 = "abc";
        str1 = "xyz";
        String str2 = "abc";
        System.out.println(str1 == str2); // false
        String str3 = new String("abc");
        System.out.println(str2 == str3); // false
    }
}

通过new 来创建一个新的String对象str3 ,这个过程是首先,JVM在常量池中找是否存在abc ,如果找到,不做任何事情,如果没找到,在常量池中创建该对象;接着由于有new 关键字,会在栈内存空间(不是字符串常量池)中创建str3 对象,并指向堆内存空间(不是字符串常量池)中的abc ,接下来进行str2 == str3 比较的时候肯定不是引用的同一个对象,所以返回false
所以说使用new创建String对象过程中是产生了俩个对象
另外Java中还提供了如何将new创建的对象手动放入常量池,看以下代码:

public class Test {
    public static void main(String[] args) {
        String str1 = "abc";
        str1 = "xyz";
        String str2 = "abc";
        System.out.println(str1 == str2); // false
        String str3 = new String("abc");
        System.out.println(str2 == str3); // false
        String str4 = str3.intern();
        System.out.println(str2 == str4); // true
    }
}

这样结果就返回的是true

StringBuilder VS StringBuffer

通过上面的介绍我们简单的了解了String是不可变的,是一个常量,因此当我们使用String来构建一个动态的字符串对于内存的开销是特别大的,因为对于每个字符串我们都得分配新的内存空间
所以在Java开发初就提供了StringBuffer JavaAPI对于StringBuffer 的说明是
A thread-safe, mutable sequence of characters.
这个可变的类来创建动态的字符数组,而这个只需要分配一次内存,这与String相比是节省了大量的内存空间,另外,由于其是线程安全的,所以其所有的public 方法都是synchronized ,而这样的设计就会导致,当开发人员是在单线程的环境中时,就会增加额外的开销,而且大多数对于StringBuffer 的使用基本上都是在单线程的环境中,所以开发人员根本不需要对于synchronized 的额外开销,所以在jdk1.5之后提供一个新的类StringBuilder ,这个类不是线程安全的也不是同步的,但是其使用更加方便而且在单线程环境中更加快速

性能测试

下面通过代码进行测试当创建动态的字符数组时String、StringBuffer、StringBuilder 三者需要的时间对比,测试代码如下:

public class Test {
    public static void main(String[] args) {
        new Test().constructDynamicString();
    }
    public void constructDynamicString()
    {
        long startTime = System.currentTimeMillis();
        String s = "test";
        //StringBuffer s = new StringBuffer("test");
        //StringBuilder s = new StringBuilder("test");
        for(int i=0; i<100000; i++)
        {
            s += "concat";
            //s.append("concat");
        }

        long endTime = System.currentTimeMillis();

        System.out.println("Concat time ====== " + (endTime - startTime) + "ms");
    }
}

结果如下:

String创建时间: 43144ms
StringBuffer创建时间: 14ms
StringBuilder创建时间: 14ms

第一次测试结果都是14ms,应该是数字较小,循环1000000次,统计结果

StringBufferConcat time ====== 80ms
StringBuilderConcat time ====== 43ms

通过以上测试可以看出,在创建动态字符串过程中,使用String需要的时间最久,而使用StringBuilder时间最短,而且创建的次数越多这个性能越明显。

总结

通过以上的分析,那么在实际使用中我们怎么选择呢?

1、如果创建静态的字符串而且在程序中是不变的,那么选择String
2、如果是创建动态字符串,并且是在多线程中使用,那么用StringBuffer
3、其他的都使用StringBuilder,并且大多数情况下都是使用StringBuilder

猜你喜欢

转载自blog.csdn.net/YaboSun/article/details/81012554