使用场景 String、StringBuffer、StringBuilder都可以应用于字符串的处理,但是在内部实现上还是有区别的。
在String内部可以发现有这样的代码private final char value[];
是用来存储字符用的,final修饰的就表明一旦初始化就无法更改,那么也就导致对String的操作始终都是产生新的String对象,这样的话,频繁更改String字符串,会导致大量的内存分配,从而给GC造成很大压力,影响程序的性能
StringBuffer/StringBuilder内部默认都是创建容量为16的char[] value;
数组,当使用append追加字符串的时候,会首先判断添加的字符串长度是否已经超过数组剩余的长度,如果没有则直接在末尾追加,并计算剩余的可用长度,如果超过,则会进行扩容的操作,重新计算新的长度(int newCapacity = (value.length << 1) + 2;
)并创建一个新的char[]数组,将原来的数组copy过去(value = Arrays.copyOf(value,newCapacity(minimumCapacity));
),通过源代码分析,可以发现StringBuffer与StringBuilder本质上的区别就是:StringBuffer支持多线程的,内部使用同步操作的机制,而StringBuilder是线程不安全的,只能使用在单线程的场景
通过对比分析,我们可以知道如果是字符串频繁的修改操作,建议使用StringBuffer/StringBuilder,而如果是在多线程的应用场景的话,选择StringBuffer,单线程就选择StringBuilder,还有一点我们必须要注意,StringBuffer/StringBuilder默认创建的时候指定的容量大小为16,我们可以根据实际的应用场景指定初始化的大小,以避免频繁的扩容操作,从而影响程序的性能
性能比较 我们可以简单地比较一下使用String、StringBuffer与StringBuilder的性能究竟如何,可以通过获取程序的执行时间(当然我们也可以使用jmh-core去更详细地比较相关的性能指标,这个在后续的文章当中专门介绍一下)
定义一个接口StringTestService
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.sherman.service;public interface StringTestService { void testString (int loopCount) ; void testStringBuffer (int loopCount) ; void testStringBuilder (int loopCount) ; void testStringBufferBySpecifiedCapacity (int loopCount) ; void testStringBuilderBySpecifiedCapacity (int loopCount) ; }
实现接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package com.sherman.service.impl;import com.sherman.service.StringTestService;import org.springframework.stereotype.Service;@Service public class StringTestServiceImpl implements StringTestService { @Override public void testString (int loopCount) { long start = System.currentTimeMillis(); String str = "" ; for (int i = 0 ; i < loopCount; i++) { str += String.format("loop:%s" , i); } long end = System.currentTimeMillis(); System.out.println(String.format("testString spend total time: %d ms" , end - start)); } @Override public void testStringBuffer (int loopCount) { long start = System.currentTimeMillis(); StringBuffer sb = new StringBuffer (); for (int i = 0 ; i < loopCount; i++) { sb.append(String.format("loop:%s" , i)); } long end = System.currentTimeMillis(); System.out.println(String.format("testStringBuffer spend total time: %d ms" , end - start)); } @Override public void testStringBuilder (int loopCount) { long start = System.currentTimeMillis(); StringBuilder sb = new StringBuilder (); for (int i = 0 ; i < loopCount; i++) { sb.append(String.format("loop:%s" , i)); } long end = System.currentTimeMillis(); System.out.println(String.format("testStringBuilder spend total time: %d ms" , end - start)); } @Override public void testStringBufferBySpecifiedCapacity (int loopCount) { long start = System.currentTimeMillis(); StringBuffer sb = new StringBuffer (loopCount); for (int i = 0 ; i < loopCount; i++) { sb.append(String.format("loop:%s" , i)); } long end = System.currentTimeMillis(); System.out.println(String.format("testStringBufferBySpecifiedCapacity spend total time: %d ms" , end - start)); } @Override public void testStringBuilderBySpecifiedCapacity (int loopCount) { long start = System.currentTimeMillis(); StringBuilder sb = new StringBuilder (loopCount); for (int i = 0 ; i < loopCount; i++) { sb.append(String.format("loop:%s" , i)); } long end = System.currentTimeMillis(); System.out.println(String.format("testStringBuilderBySpecifiedCapacity spend total time: %d ms" , end - start)); } }
可以发现实现的服务里面有很多重复的代码,针对这种情况,我们可以使用aop的方式进行重构,有兴趣的同学也可以自己去尝试一下,我也打算放在单独的文章当中介绍如何通过aop的方式实现日志的记录。暂且先忍受一下,重点是比较执行时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.sherman;import com.sherman.service.StringTestService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.CommandLineRunner;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication public class App implements CommandLineRunner { @Autowired private StringTestService stringTestService; public static void main ( String[] args ) { SpringApplication.run(App.class, args); System.out.println( "Hello World!" ); } @Override public void run (String... args) throws Exception { System.out.println("程序的真正入口地址" ); int loop = 1000000 ; stringTestService.testString(loop); stringTestService.testStringBuffer(loop); stringTestService.testStringBuilder(loop); stringTestService.testStringBufferBySpecifiedCapacity(loop); stringTestService.testStringBuilderBySpecifiedCapacity(loop);} }
程序的执行结果如下: