首页 > 编程知识 正文

用stringbuffer做拼接,stringbuffer拼接字符串

时间:2023-05-04 01:14:44 阅读:283073 作者:549

StringBuilder做字符串拼接高效的原因 1.与String相比

通过分析源码,发现两者底层都是用一个数组来存储字符

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

这里需要注意,StringBuilder本身并没有定义value数组,我们需进入其父类AbstractStringBuilder中,可以发现用来存储字符的数组value.

/*** The value is used for character storage*/char value[];

可以发现,String底层的数组是用final修饰的,是一个数组常量,而StringBuilder底层用来接收存储字符的数组是一个变量.所以前者在创建之后是没法更改,而后者可以

这里我们看一段代码

package reason;public class Test {public static void main(String[] args){ String str = "不变"; System.out.println(str); str = "变了" System.out.println(str); }}


结果却是str从最初的"不变",到后来的"变了",字符串str发生了改变,但String底层的数组不是常量吗?为什么值会发生改变?

其实在内存中String发生了这样的变化:

在内存中,我们先创建了一个str对象,并且赋值"不变",之后其实并不是在原有创建的str对象上作更改,而是又创建了一个新的字符串对象,并且赋值变了,把之前的引用类型变量str指向新创建的对象,而之前创建的对象处于等待被回收的状态,如果没有被调用,就会被JVM提供的垃圾回收机制给回收掉.

到这里我们可以发现,之所以String本身做字符串拼接执行速度慢,是因为其本质上是一个不断创建新对象,并且回收旧对象的过程.那说到StringBuilder和StringBuffer,它们创建的对象是变量,对变量操作就是对对象操作,中间不存在对象的创建和回收,所以速度比String快

那真的是这样吗?我们进入到StringBuilder封装后的源代码,查看其append方法

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

发现StringBuilder在使用append方法时,如果传进来的参数是String类型 的,会去调用其父类的append方法,也就是AbstractStringBuilder的append方法,我们进入AbstractStringBuilder

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; }

发现当传入的字符串是null时,会去调用appendNull方法,找到appendNull方法的源码

private AbstractStringBuilder appendNull() { int c = count; ensureCapacityInternal(c + 4); final char[] value = this.value; value[c++] = 'n'; value[c++] = 'u'; value[c++] = 'l'; value[c++] = 'l'; count = c; return this; }

这里需要注意,count是用来记录存储字符的数组的长度首先会用一个变量c存放当前数组的长度,再去调用ensureCapacityInternal方法来进行数组扩容.(等会append方法也会调用这个方法)我们先找到ensureCapacityInternal的源码,它和append,appendNull一样在AbstractStringBuilder类里

private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) { value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } }

发现了这个方法其实就是再做数组的扩容,利用的是Arrays类里的copyOf进行数组的扩容

回到上面appendNull方法中,会将当前数组的长度加4作为参数传入ensureCapacityInternal方法,将value数组扩容四个大小,并且把最后四个值改为null,所以我们利用append方法拼接一个值为null的字符串,得到的也是null

public class Test1 { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub StringBuilder buff = new StringBuilder(); System.out.println(buff); String string = null; buff.append(string); System.out.println(buff); }}

测试发现结果的确为null

在回到append方法中,如果传的字符串不为空时,会将字符串的长度和当前value数组的长度加在一起作为参数调用ensureCapacityInternal方法,获得扩容后的数组,再去调用String类的getChars方法.进入String源码,查看getChars方法

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { if (srcBegin < 0) { throw new StringIndexOutOfBoundsException(srcBegin); } if (srcEnd > value.length) { throw new StringIndexOutOfBoundsException(srcEnd); } if (srcBegin > srcEnd) { throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); } System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); }

能够看到这个方法是利用System.arraycopy方法将value数组的值加上新传进来的String字符串的拼接到新数组dst当中

最后append方法将扩容后的本实例传回去,做到了字符串拼接的效果.可以看到整个过程中真的没有创建新的对象,一切都是在对value这个数组变量进行操控.

但是仔细考虑还是会发现一些问题,因为数组一旦创建长度没法改变,无论是利用System.arraycopy方法还是Arrays.CopyOf方法,实际上都是再new一个新的数组来存放数据.在StringBuilder扩容的过程当中,虽说没有new一个新对象,但是当拼接的字符串不为null时,会new两个新的数组.而利用"+"这种形式虽说会创建一个新的字符串对象,但是每次创对象时只要再new一个新数组就行了.所以说实际上StringBuilder也会出现不断创建新的,并且回收旧的过程,只不过从对象变成了数组.那到底为什么StringBuilder会比String快那么多呢?

这时候决定查看以下反编译的结果,发现String用"+"这种方式做字符串拼接时,竟然调用了StringBuffer的append方法.what?

翻阅Thinking in java发现,原来String在做大量拼接时,会默认调用StringBuffer的append方法.这样一切就说通过了,String比StringBuilder慢的原因是因为在大量拼接时,它会不断的new一个新的StringBuffer类,然后调用append方法,会再new两个新的数组,这样就出现了大量的浪费资源情况,效率也比StringBuilder慢很多

2.与StringBuffer相比

原因很简单,查阅源码就能发现,StringBuilder里的append方法没有synchronized关键字,所以它为了追求速度放弃了线程的安全性,如下

@Overridepublic synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this;} @Override public StringBuilder append(String str) { super.append(str); return this; }

super.append(str);
return this;
}

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

但是在单一线程的情况下,StringBuilder不会出现线程安全的问题,所以建议在单线程时使用StringBuilder会更快

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。