Java String、StringBuffer、StringBuilder深度剖析

来源:互联网 发布:excel无法黏贴数据 编辑:程序博客网 时间:2024/05/19 11:17

三种字符串构造方式

  1. String方式
String testString = "a";for (int i = 0; i < 10; i++) {    testString += "b";}
  1. StringBuffer方式
StringBuffer sbuf = new StringBuffer();for (int i = 0; i < 10; i++) {    sbuf.append("b");}
  1. StringBuilder方式
StringBuilder sbud = new StringBuilder();for (int i = 0; i < 10; i++) {    sbud.append("b");}

三种方案剖析

利用String的 a+=”b”这种方式来构造字符串在我们平时用的比较多,用起来也很方便,但是总有一些有开发经验的前辈们会说:“尽量少使用这种方式,应该多使用StringBuffer、StringBuilder等方式来构造,这种方式会降低整个程序的性能!”,但是至于为什么使用这种方式会降低程序的性能,我们无从得知。今天我编写这篇博客的目的就是对这三种方式进行深入剖析,让读者真正的了解为什么!

1. String构造方式解析
我们常常会使用String testString = "a"; testString += "b";这种方式来构造字符串,但是却被告知这种方式会降低性能,这到底是怎么一回事呢?接下来我会通过分析源码的方式给大家仔细分析为什么!

String.java

// 为什么说String不是基本数据类型的原因也可以从这段代码中看出来// String字符串的实现方式是建立在字符数组之上的public final class String    implements java.io.Serializable, Comparable<String>, CharSequence {    /** The value is used for character storage. */    /** String的内容是存放在字符数组中的. final意味着字符串的内容是只读的,不可修改的*/    private final char value[];    ....}

大家从这段代码中看到了什么?final关键字,这意味着String从一开始初始化,它的内容是不允许修改的,那么我们平时通过String testString = "a"; testString = "b";这种方式对字符串重新赋值是怎么做到的呢,既然我们不能修改String的值,那就只能重新创建一个String对象了,然后让testString指向新的String对象的地址即可,这就意味着每一次执行赋值(testString=“a”或者testString += “a”)都会重新创建一个对象,然而创建对象和销毁对象是一个浪费时间和空间的过程,所以有过经验的开发者都会说,尽量不要使用这种方式构造字符串,这也是为什么说这种方式会影响程序性能的原因了。

2. StringBuffer方式的构造

AbstractStringBuilder.java

abstract class AbstractStringBuilder implements Appendable, CharSequence {    /**     * The value is used for character storage.     */    /* 存放字符串的字符数组 */    char[] value;    ......    public AbstractStringBuilder append(String str) {        if (str == null)            return appendNull();        int len = str.length();        ensureCapacityInternal(count + len);        str.getChars(0, len, value, count);        count += len;        return this;    }}

StringBuffer.java

// 继承AbstractStringBuilder,所以说也继承了char[] value; 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.     */     /* 缓存上一次toString方法返回的值,每次append或者其他修改操作的时候都会清空*/    private transient char[] toStringCache;    ......      public synchronized int offsetByCodePoints(int index, int codePointOffset) {        return super.offsetByCodePoints(index, codePointOffset);    }    @Override    public synchronized StringBuffer append(Object obj) {        toStringCache = null;        super.append(String.valueOf(obj));        return this;    }    @Override    public synchronized StringBuffer append(String str) {        toStringCache = null;        super.append(str);        return this;    }    ......     @Override    public synchronized String toString() {        // 生成String对象        if (toStringCache == null) {            toStringCache = Arrays.copyOfRange(value, 0, count);        }        return new String(toStringCache, true);    }

大家从上面的代码有没有发现什么?synchronized关键字、char[] value已经没有用final修饰了。synchronized修饰意味着这是线程安全的,适合多线程使用;value不再使用final修饰意味着每次insert、append只是纯粹的修改了value的值,而没有重新创建新的String对象,减少了对象创建和销毁的过程,更高效。

总结一下两点
1. synchronized修饰,可以多线程使用,但是锁的争用降低效率,如果单线程想要使用此方法构造,同样会存在上锁和释放锁的过程,效率低下。
2. char[] value已经没有用final修饰了,字符串的构造过程只是值的修改过程,没有对象的创建,效率更高。

3. StringBuilder构造方式的剖析

AbstractStringBuilder.java

// 和StringBuffer一样的父类abstract class AbstractStringBuilder implements Appendable, CharSequence {    /**     * The value is used for character storage.     */    /* 存放字符串的字符数组 */    char[] value;    ......    public AbstractStringBuilder append(String str) {        if (str == null)            return appendNull();        int len = str.length();        ensureCapacityInternal(count + len);        str.getChars(0, len, value, count);        count += len;        return this;    }}

StringBuilder.java

// 和StringBuffer一样继承AbstractStringBuilder// 字符串存放在AbstractStringBuilder的value中public final class StringBuilder    extends AbstractStringBuilder    implements java.io.Serializable, CharSequence{    ......     @Override    public StringBuilder append(Object obj) {        return append(String.valueOf(obj));    }    @Override    public StringBuilder append(String str) {        super.append(str);        return this;    }    ......     @Override    public String toString() {        // Create a copy, don't share the array        return new String(value, 0, count);    }}

大家可以看到方法修饰少了synchronized关键字,少了toStringCache(并没有什么特别的影响),除此之外和StringBuffer是差不多的。少了synchronized更方便单线程使用,提高了效率。

总结

通过上面我们分别对String、StringBuffer、StringBuilder的源码分析,总结三种方式进行字符串构造的优缺点如下:
1. String字符串构造方式会不断的创建和销毁对象,时间和空间浪费大
2. StringBuffer方式不会频繁的创建和销毁对象,性能大大提升,而且操作是多线程安全的,所以适合多线程的字符串构造,但是对于单线程来说仍然存在上锁和释放锁的过程,增加了构造的时间,不利于单线程操作
3. StringBuilder大体上同StringBuffer一样,但是没有加入多线程的安全机制,不适合多线程使用,但是由于没有引入多线程安全的机制,所以去除了上锁和释放锁的过程,减少了构造的时间,适合单线程使用

0 0