【Java深入理解】String、StringBuilder、StringBuffer的区别

01/29 11:00
阅读数 63

在我们学习String类的时候,也会学习到StringBuilder和StringBuffer,但是他们之间有什么区别呢? 当然他们在具体的代码实现上、内存分配上以及效率上都有着不同(我这里以JDK8为例);

一、代码实现

String

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
   
   
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    /**
     * Class String is special cased within the Serialization Stream Protocol.
     *
     * A String instance is written into an ObjectOutputStream according to
     * <a href="{@docRoot}/../platform/serialization/spec/output.html">
     * Object Serialization Specification, Section 6.2, "Stream Elements"</a>
     */
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];

    /**
     * Initializes a newly created {@code String} object so that it represents
     * an empty character sequence.  Note that use of this constructor is
     * unnecessary since Strings are immutable.
     */
    public String() {
   
   
        this.value = "".value;
    }

    /**
     * Initializes a newly created {@code String} object so that it represents
     * the same sequence of characters as the argument; in other words, the
     * newly created string is a copy of the argument string. Unless an
     * explicit copy of {@code original} is needed, use of this constructor is
     * unnecessary since Strings are immutable.
     *
     * @param  original
     *         A {@code String}
     */
    public String(String original) {
   
   
        this.value = original.value;
        this.hash = original.hash;
    }

从这里我们可以看出,String的底层结构是private final char value[];所以String的值是不可变的,并且实现了Comparable接口,所以是支持比较的。

StringBuilder

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
   
   

    /** use serialVersionUID for interoperability */
    static final long serialVersionUID = 4383685877147921099L;

    /**
     * Constructs a string builder with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuilder() {
   
   
        super(16);
    }

    /**
     * Constructs a string builder with no characters in it and an
     * initial capacity specified by the {@code capacity} argument.
     *
     * @param      capacity  the initial capacity.
     * @throws     NegativeArraySizeException  if the {@code capacity}
     *               argument is less than {@code 0}.
     */
    public StringBuilder(int capacity) {
   
   
        super(capacity);
    }

    /**
     * Constructs a string builder initialized to the contents of the
     * specified string. The initial capacity of the string builder is
     * {@code 16} plus the length of the string argument.
     *
     * @param   str   the initial contents of the buffer.
     */
    public StringBuilder(String str) {
   
   
        super(str.length() + 16);
        append(str);
    }

    /**
     * Constructs a string builder that contains the same characters
     * as the specified {@code CharSequence}. The initial capacity of
     * the string builder is {@code 16} plus the length of the
     * {@code CharSequence} argument.
     *
     * @param      seq   the sequence to copy.
     */
    public StringBuilder(CharSequence seq) {
   
   
        this(seq.length() + 16);
        append(seq);
    }

    @Override
    public StringBuilder append(Object obj) {
   
   
        return append(String.valueOf(obj));
    }

    @Override
    public StringBuilder append(String str) {
   
   
        super.append(str);
        return this;
    }

从这里我们可以看出StringBuilder有一个抽象父类,他是没有实现Comparable接口,明显不支持比较。我们看到AbstractStringBuilder类中,value是可变的,并不像String有final修饰

AbstractStringBuilder

abstract class AbstractStringBuilder implements Appendable, CharSequence {
   
   
    /**
     * The value is used for character storage.
     */
    char[] value;

    /**
     * The count is the number of characters used.
     */
    int count;

    /**
     * This no-arg constructor is necessary for serialization of subclasses.
     */
    AbstractStringBuilder() {
   
   
    }

StringBuffer

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
   
   

    /**
     * A cache of the last value returned by toString. Cleared
     * whenever the StringBuffer is modified.
     */
    private transient char[] toStringCache;

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    static final long serialVersionUID = 3388685877147921107L;

    /**
     * Constructs a string buffer with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuffer() {
   
   
        super(16);
    }

    /**
     * Constructs a string buffer with no characters in it and
     * the specified initial capacity.
     *
     * @param      capacity  the initial capacity.
     * @exception  NegativeArraySizeException  if the {@code capacity}
     *               argument is less than {@code 0}.
     */
    public StringBuffer(int capacity) {
   
   
        super(capacity);
    }

    /**
     * Constructs a string buffer initialized to the contents of the
     * specified string. The initial capacity of the string buffer is
     * {@code 16} plus the length of the string argument.
     *
     * @param   str   the initial contents of the buffer.
     */
    public StringBuffer(String str) {
   
   
        super(str.length() + 16);
        append(str);
    }

    /**
     * Constructs a string buffer that contains the same characters
     * as the specified {@code CharSequence}. The initial capacity of
     * the string buffer is {@code 16} plus the length of the
     * {@code CharSequence} argument.
     * <p>
     * If the length of the specified {@code CharSequence} is
     * less than or equal to zero, then an empty buffer of capacity
     * {@code 16} is returned.
     *
     * @param      seq   the sequence to copy.
     * @since 1.5
     */
    public StringBuffer(CharSequence seq) {
   
   
        this(seq.length() + 16);
        append(seq);
    }

    @Override
    public synchronized int length() {
   
   
        return count;
    }

    @Override
    public synchronized int capacity() {
   
   
        return value.length;
    }

而StringBuffer同样是继承一个抽象父类AbstractStringBuilder,它的底层结构同样是可变的char[] value数组也没有实现Comparable接口。

StringBuffer和StringBuilder虽然结构是一样的,但还是有不同点。StringBuffer多了几样东西。
如:
1、toStringCache它是用于执行toString()方法时,把值保存到变量中,下次继续执行toString()方法时,可以直接从变量中取出来。变量是transient 修饰的,代表着不可序列化。
2、还有一个本质上的区别,StringBuffer的方法是synchronized修饰的,代表着是线程安全的。


二、性能效率

String

//        Stirng
        String str = "";
        long oldTime = System.currentTimeMillis();

        for (int i = 0; i < 100000; i++) {
   
   
            str += "a";
        }
        System.out.printf("耗时:%d ms",(System.currentTimeMillis() - oldTime));

耗时:11114 ms

我这里使用for循环,每循环一次,都往str中拼接字符串"a",总共循环10万次。执行时间达到了平均11秒,太可怕了,效率实在太慢太慢了。

StringBuilder

//      StringBuilder
        StringBuilder str = new StringBuilder("");
        long oldTime = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {
   
   
            str.append("a");
        }
        System.out.printf("耗时:%d ms",(System.currentTimeMillis() - oldTime));

耗时:279 ms

我这里使用StringBuilder的append方法进行拼接字符串,拼接1000万次,平均执行时间只需279毫秒,这里对比String可以看出,在大量拼接字符串的时候,String的劣势很明显。
当然StringBuilder虽然够快,但也有他的劣势,在多线程的环境下是线程不安全的。

StringBuffer

//        StringBuffer
        StringBuffer str = new StringBuffer("");
        long oldTime = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {
   
   
            str.append("a");
        }
        System.out.printf("耗时:%d ms",(System.currentTimeMillis() - oldTime));

耗时:547 ms

这里同样使用StringBuffer拼接字符串,同样1000万次,耗时平均550毫秒。虽然它没有StringBuilder快,但是它可以保证线程安全,我们前面讲过StringBuffer的方法是synchronized修饰的。当然线程安全就代表着它性能效率会被降低了,因为我们的线程需要去竞争锁,竞争到锁的线程才可以执行,虽然有偏向锁,性能难免会被降低。

三、内存分配

JVM内存区域划分如下(自画,如果有觉得错的地方,可以私信我修正哈)

在这里插入图片描述

String

String str = “a” + “b” + “c”; //我们以这段代码为例,我们来看看内存中是如何分配的

在这里插入图片描述
【总所周知,字符串常量池在JDK8中从方法区搬到了堆中!】
我们可以看到,在使用String进行拼接不同字符串的时候,每次拼接相连,都会在常量池中创建相应的字面量。这是因为String的value是final修饰的,并不能直接修改,所以会一直创建新的对象,并重新进行赋值。

StringBuilder和StringBuffer

在这里插入图片描述

StringBuilder和StringBuffer在内存分配上是一样的,只会改自身对象中的value值,不会在堆中重复创建新的字面量进行重新赋值。但拼接完需要调用toString()方法转成String类型,这时候才会在堆中创建一个"abc"字面量

完结

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部