StringBuffer与StringBuilder

说明:本文是阅读《Java程序性能优化》(作者:葛一明)一书中关于StringBuffer与StringBuilder一节的笔记。


一、String常量与变量的累加操作

1、String常量的累加操作

由于String对象具有不可变性,所以String对象一旦生成就无法被改变,所以如下所示的字符串累加操作总共会产生7个String对象,因此从理论上讲这段代码的效率并不高。

String str = "String" + "and" + "StringBuilder" + "append";

此时就可以用到StringBuffer或者StringBuilder类了,使用StringBuilder类完成同样的功能如下:

StringBuilder sb = new StringBuilder();
sb.append("String").append("and").append("StringBuilder").append("append");
使用StringBuilder后仅仅会生成4个String对象与1个StringBuilder对象,效率会高于直接累加的方式。但是,如果将以上两段代码分别执行100000次,得到的结果却是令人惊讶的,在我的机器上测试得到的用时分别是1ms(直接累加)与大概40ms(使用StringBuilder)左右。为什么呢?使用反编译工具对第一段代码进行反编译,得到的结果如下:

String str = "StringandStringBuilderappend";
从反编译结果来看, 对于常量字符串的累加,Java在编译时就做了优化,对于在编译时就能确定取值的字符串的操作,在编译时就进行了计算,因此在运行时该段代码并没有像想象中那样生成7个String对象。而对于上面的第二段代码,反编译得到的结果与源代码一样,所以在运行时StringBuilder对象和append()方法都被如实的调用,所以相比之下,第一段代码的执行效率却更高。
2、String变量的累加操作

如下代码所示,现在将每个字符串都定义在一个变量中,然后再对这边变量进行累加操作,这样在编译时就无法确定str变量的取值:

String s1 = "String";
String s2 = "and";
String s3 = "StringBuilder";
String s4 = "append";
String str = s1 + s2 + s3 + s4;
书上说的是通过反编译后,生成的代码会是如下所示的样子:

String s1 = "String";
String s2 = "and";
String s3 = "StringBuilder";
String s4 = "append";
String str = (new StringBuilder(String.valueOf(s1))).append(s2).append(s3).append(s4).toString();
但是我通过JD-GUI反编译工具查看得到的反编译代码并不是这样的(JDK1.6u45),却是和原来的代码保持一模一样,有可能是使用的编译器不一样的原因吧。接着我实验了常量字符串与变量字符串混合使用的情况,源代码如下:

String s1 = "String";
String s2 = "and";
		
String str1 = "Hello" + "String" + s1 + s2 + "Hello" + "StringBuilder";
String str2 = s1 + s2 + "Hello" + "StringBuilder";
String str3 = s1 + "Hello" + "StringBuilder" + s2;
接着使用反编译工具反编译得到的代码如下:可见,在由常量字符串和变量字符串混合累加的情况下,开始部分的常量字符串会得到优化,在编译时就计算出来了,其余情况下都不会得到优化。

String s1 = "String";
String s2 = "and";
    
String str1 = "HelloString" + s1 + s2 + "Hello" + "StringBuilder";
String str2 = s1 + s2 + "Hello" + "StringBuilder";
String str3 = s1 + "Hello" + "StringBuilder" + s2;

总之,对于字符串的累加操作,在某些情况下编译器会做一定的优化,但是别依赖于编译器的这些优化,因为编译器也不是绝对的那么智能,还是应该在编写程序时就注重代码的优化。

二、StringBuffer与StringBuilder

1、StringBuffer与StirngBuilder的功能都几乎一样,最大的不同之处在于StringBuffer对它的几乎所有的方法都做了同步,而StringBuilder并没有做任何同步。由于方法不同需要一定的系统资源,所以StringBuilder的效率要好于StringBuffer,但是在多线程中,StringBuilder无法保证线程安全,而StringBuffer却可以。

2、容量参数

StringBuffer和StirngBuilder在初始化时都可以指定一个容量参数(默认是16个字节),该参数指定了它们的初始大小,如下图是它们的父类的一个构造函数:

当在追加字符串时,如果其容量已经超过了char数组长度,则需要进行扩容,扩容的函数定义如下图:

从中可以发现,扩容的策略是将原有的容量大小翻倍来申请内存空间,然后建立新的char数组,最后再将原数组中的内容复制到新的数组中,所以对于大对象的扩容会涉及大量的内存复制操作,如果能够预先估计其容量大小,则能有效的节省这些操作,从而提高效率。如下代码所示,没指定容量参数时在我的机器上执行大概需要600ms左右的时间,而指定了初始容量后大概需要500ms左右的时间。

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5000000; i++) {
	sb.append(i);
}

StringBuilder sb2 = new StringBuilder(5000000);
for (int i = 0; i < 5000000; i++) {
	sb2.append(i);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值