关于string stringbuffer stringbuilder速度的考虑

之前有面试官也问过,但是在平时并没有感觉出三者有什么太大的不同,但是最近在读《java特种兵》一书,感觉三者的文章真的很多。


一般认为在字符串的拼接时三者的顺序是:StringBuilder>StringBuffer>String。



StringBuilder是线程不安全的,StringBuffer线程安全,多线程下会锁定对象,时间略慢,String在字符串操作时会产生新的字符串,最慢。


但是,当字符串的凭借全部是常量时,这时候string的优势是最大的,通过编译器优化,字符串常量的+自动优化成一个字符串常量,此时最快,但是当String的并接中出现了变量,String的优势就没有了。

我做了一个实验,代码如下,做1w次的字符串并接

                long time = System.currentTimeMillis();
		
		StringBuffer c = new StringBuffer("a");
		for (int i = 0; i < 10000; i++) {
			c.append(i+"喝酒啊哈哈哈哈哈哈0");
		}
		System.out.println("StringBuffer 的时间:"+(System.currentTimeMillis()-time));
		time = System.currentTimeMillis();
		c=null;//帮助GC回收,避免长时间占用堆,排除下面oom和这有关系。
		
		StringBuilder a = new StringBuilder("a");
		for (int i = 0; i < 10000; i++) {
			a.append(i+"喝酒啊哈哈哈哈哈哈0");
		}
		System.out.println("StringBuilder 的时间:"+(System.currentTimeMillis()-time));
		a = null;
		
		time = System.currentTimeMillis();
		String b = "a";
		for (int i = 0; i < 10000; i++) {
			b+=i+"喝酒啊哈哈哈哈哈哈0";
		}
		System.out.println("String 的时间:"+(System.currentTimeMillis()-time));



最后的输出日志:

StringBuffer 的时间:25
StringBuilder 的时间:23
[GC [PSYoungGen: 15336K->2136K(18944K)] 15336K->3672K(61952K), 0.0040222 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC [PSYoungGen: 18212K->600K(35328K)] 19748K->11360K(78336K), 0.0057828 secs] [Times: user=0.05 sys=0.01, real=0.01 secs] 
[GC [PSYoungGen: 25467K->536K(35328K)] 36227K->29730K(78336K), 0.0082693 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC [PSYoungGen: 536K->0K(35328K)] [ParOldGen: 29194K->18910K(61952K)] 29730K->18910K(97280K) [PSPermGen: 2718K->2717K(21504K)], 0.0166769 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
[GC [PSYoungGen: 24768K->32K(56320K)] 559817K->547369K(745472K), 0.0104553 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC [PSYoungGen: 32K->32K(66048K)] 547369K->547369K(755200K), 0.0040113 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 32K->0K(66048K)] [ParOldGen: 547337K->197100K(252928K)] 547369K->197100K(318976K) [PSPermGen: 2717K->2717K(21504K)], 0.0821627 secs] [Times: user=0.11 sys=0.03, real=0.08 secs] 
[GC [PSYoungGen: 243K->32K(103936K)] 590592K->590380K(793088K), 0.0050491 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC [PSYoungGen: 32K->32K(103936K)] 590380K->590380K(793088K), 0.0046632 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[Full GC [PSYoungGen: 32K->0K(103936K)] [ParOldGen: 590348K->197100K(262656K)] 590380K->197100K(366592K) [PSPermGen: 2717K->2717K(21504K)], 0.1007265 secs] [Times: user=0.22 sys=0.03, real=0.10 secs] 
[GC [PSYoungGen: 0K->0K(173568K)] 393724K->393724K(862720K), 0.0037389 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC [PSYoungGen: 0K->0K(183296K)] 393724K->393724K(872448K), 0.0032845 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 0K->0K(183296K)] [ParOldGen: 393724K->393724K(467968K)] 393724K->393724K(651264K) [PSPermGen: 2717K->2717K(21504K)], 0.0060832 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC [PSYoungGen: 0K->0K(255488K)] 393724K->393724K(944640K), 0.0020710 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 0K->0K(255488K)] [ParOldGen: 393724K->393713K(475136K)] 393724K->393713K(730624K) [PSPermGen: 2717K->2717K(21504K)], 0.1100342 secs] [Times: user=0.34 sys=0.00, real=0.11 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:2367)
	at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:130)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:114)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:415)
	at java.lang.StringBuilder.append(StringBuilder.java:132)
	at com.Demo.main(Demo.java:50)
Heap
 PSYoungGen      total 255488K, used 6040K [0x00000000eaf00000, 0x00000000fb780000, 0x0000000100000000)
  eden space 253440K, 2% used [0x00000000eaf00000,0x00000000eb4e6098,0x00000000fa680000)
  from space 2048K, 0% used [0x00000000fa880000,0x00000000fa880000,0x00000000faa80000)
  to   space 2048K, 0% used [0x00000000fa680000,0x00000000fa680000,0x00000000fa880000)
 ParOldGen       total 689152K, used 393713K [0x00000000c0e00000, 0x00000000eaf00000, 0x00000000eaf00000)
  object space 689152K, 57% used [0x00000000c0e00000,0x00000000d8e7c530,0x00000000eaf00000)
 PSPermGen       total 21504K, used 2748K [0x00000000bbc00000, 0x00000000bd100000, 0x00000000c0e00000)
  object space 21504K, 12% used [0x00000000bbc00000,0x00000000bbeaf328,0x00000000bd100000)
String最后干脆直接oom了,String的慢为什么慢,验证一下是不是因为GC的频繁活动导致的。StringBuilder在单线程下速度和StringBuilder相差不大,不需要同步。上面可以看出String在大量字符串并接的时候GC活动频繁。调大堆内存后在尝试,结果是GC的次数越少越快,尤其是fullGC的次数

把上面的循环数量级缩小到100以内日志输出:


String 的时间:5
StringBuilder 的时间:2
StringBuffer 的时间:3


String还是慢了,但是差距没有那么大了,但是还是慢,这是什么原因呢,我们都知道编译器对String+的操作其实是有优化的,String+优化成StringBuilder的append。下面模拟一下编译器的优化,做一下对比:


		long time = System.currentTimeMillis();
		String b = "a";
		for (int i = 0; i < 400; i++) {
			b+=i+"喝酒啊哈哈哈哈哈哈0";
		}
		System.out.println("String 的时间:"+(System.currentTimeMillis()-time));
		b=null;
		time = System.currentTimeMillis();
		
		String a = "a";
		
		for (int i = 0; i < 400; i++) {
			StringBuilder tmp = new StringBuilder();
			tmp.append(a).append(i+"喝酒啊哈哈哈哈哈哈0");
			a=tmp.toString();
		}
		System.out.println("String 的时间:"+(System.currentTimeMillis()-time));

时间主要是花费在了对象的创建上,日志输出:

String 的时间:4
String 的时间:6
Heap
 PSYoungGen      total 18944K, used 13440K [0x00000000eaf00000, 0x00000000ec400000, 0x0000000100000000)
  eden space 16384K, 82% used [0x00000000eaf00000,0x00000000ebc20240,0x00000000ebf00000)
  from space 2560K, 0% used [0x00000000ec180000,0x00000000ec180000,0x00000000ec400000)
  to   space 2560K, 0% used [0x00000000ebf00000,0x00000000ebf00000,0x00000000ec180000)
 ParOldGen       total 43008K, used 0K [0x00000000c0e00000, 0x00000000c3800000, 0x00000000eaf00000)
  object space 43008K, 0% used [0x00000000c0e00000,0x00000000c0e00000,0x00000000c3800000)
 PSPermGen       total 21504K, used 2716K [0x00000000bbc00000, 0x00000000bd100000, 0x00000000c0e00000)
  object space 21504K, 12% used [0x00000000bbc00000,0x00000000bbea72b8,0x00000000bd100000)

理论上分析:


String在做字符串+操作时,会优化成StringBuilder,当String长度超过StringBuilder的初始长度16时,此时StringBuilder会扩容,如果append的字符串长度小于32,则StringBuilder扩容为32,否则扩容为String的长度。随着字符串+运算,超过这个是肯定的,这样每一次都会导致StringBuilder扩容,但是下次在+操作时又要重新申请一堆内存,不但新申请的占用了堆内存,而且原有字符串在扩容时还存在,这样更加消耗堆内存,垃圾随着循环次数的增加不断增加,引起GC的频繁回收,增加了操作的时间。


StringBuilder和StringBuffer在做append操作时应优先append长的字符串,避免每次append时的扩容操作的发生


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值