占小狼-Java进阶之路-浅谈StringBuffer
来源:互联网 发布:springmvc 项目源码 编辑:程序博客网 时间:2024/04/30 13:40
浅谈StringBuilder
连接符号 "+" 本质
在 浅谈Java String内幕(1) 中,字符串变量(非final修饰)通过 "+" 进行拼接,在编译过程中会转化为StringBuilder对象的append操作,注意是编译过程,而不是在JVM中。
public class StringTest { public static void main(String[] args) { String str1 = "hello "; String str2 = "java"; String str3 = str1 + str2 + "!"; String str4 = new StringBuilder().append(str1).append(str2).append("!").toString(); }}
上述 str3 和 str4 的执行效果其实是一样的,不过在for循环中,千万不要使用 "+" 进行字符串拼接。
public class test { public static void main(String[] args) { run1(); run2(); } public static void run1() { long start = System.currentTimeMillis(); String result = ""; for (int i = 0; i < 10000; i++) { result += i; } System.out.println(System.currentTimeMillis() - start); } public static void run2() { long start = System.currentTimeMillis(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < 10000; i++) { builder.append(i); } System.out.println(System.currentTimeMillis() - start); }}
在for循环中使用 "+" 和StringBuilder进行1万次字符串拼接,耗时情况如下:
1、使用 "+" 拼接,平均耗时 250ms;
2、使用StringBuilder拼接,平均耗时 1ms;
for循环中使用 "+" 拼接为什么这么慢?下面是run1方法的字节码指令:
5 ~ 34 行对应for循环的代码,可以发现,每次循环都会重新初始化StringBuilder对象,导致性能问题的出现。
性能问题
StringBuilder内部维护了一个char[]类型的value,用来保存通过append方法添加的内容,通过 new StringBuilder()
初始化时,char[]的默认长度为16,如果append第17个字符,会发生什么?
void expandCapacity(int minimumCapacity) { int newCapacity = value.length * 2 + 2; if (newCapacity - minimumCapacity < 0) newCapacity = minimumCapacity; if (newCapacity < 0) { if (minimumCapacity < 0) // overflow throw new OutOfMemoryError(); newCapacity = Integer.MAX_VALUE; } value = Arrays.copyOf(value, newCapacity);}
如果value的剩余容量,无法添加全部内容,则通过expandCapacity(int minimumCapacity)
方法对value进行扩容,其中minimumCapacity = 原value长度 + append添加的内容长度。1、扩大容量为原来的两倍 + 2,为什么要 + 2,而不是刚好两倍?
2、如果扩容之后,还是无法添加全部内容,则将 minimumCapacity 作为最终的容量大小;
3、利用
System.arraycopy
方法对原value数据进行复制;public class StringBuilderTest { public static void main(String[] args) { int sum = 0; final int capacity = 40000000; for (int i = 0; i < 100; i++) { sum += cost(capacity); } System.out.println(sum / 100); } public static long cost(int capacity) { long start = System.currentTimeMillis(); StringBuilder builder = new StringBuilder(capacity); for (int i = 0; i < 10000000; i++) { builder.append("java"); } return System.currentTimeMillis() - start; }}
执行一千万次append操作,不同初始容量的耗时情况如下:
1、容量为默认16时,平均耗时110ms;
2、容量为40000000时,不会发生复制操作,平均耗时85ms;
通过以上数据可以发现,性能损耗不是很严重。
内存问题
1、StringBuilder内部进行扩容时,会新建一个大小为原来两倍+2的char数组,并复制原char数组到新数组,导致内存的消耗,增加GC的压力。
2、StringBuilder的toString方法,也会造成char数组的浪费。
public String toString() { // Create a copy, don't share the array return new String(value, 0, count);}
System.arraycopy()
复制StringBuilder中char数组的数据,这样StringBuilder的char数组就白白浪费了。
重用StringBuilder
public class StringBuilderHolder { private final StringBuilder sb; public StringBuilderHolder(int capacity) { sb = new StringBuilder(capacity); } public StringBuilder resetAndGet() { sb.setLength(0); return sb; }}
通过
sb.setLength(0)
方法可以把char数组的内存区域设置为0,这样char数组重复使用,为了避免并发访问,可以在ThreadLocal中使用StringBuilderHolder,使用方式如下:private static final ThreadLocal<StringBuilderHolder> stringBuilder= new ThreadLocal<StringBuilderHolder>() { @Override protected StringBuilderHolder initialValue() { return new StringBuilderHolder(256); }};StringBuilder sb = stringBuilder.get().resetAndGet();
不过这种方式也存在一个问题,该StringBuilder实例的内存空间一直不会被GC回收,如果char数组在某次操作中被扩容到一个很大的值,可能之后很长一段时间都不会用到如此大的空间,就会造成内存的浪费。
总结
虽然使用默认的StringBuilder进行字符串拼接操作,性能消耗不是很严重,但在高性能场景下,还是推荐使用ThreadLocal下可重用的StringBuilder方案。
- 占小狼-Java进阶之路-浅谈StringBuffer
- 占小狼-Java进阶之路-浅谈Java String内幕(一)
- 占小狼-Java进阶之路-浅谈Java String内幕(二)
- 占小狼之-Java进阶之路-深入分析String.intern和String常量的实现原理
- 占小狼之-JVM-JVM源码分析之Java类加载过程
- 占小狼之-JVM-JVM源码分析之Java对象的创建过程
- 占小狼之-JVM-JVM内存的那些事
- java中StringBuffer浅谈
- Java基础之String,StringBuffer与StringBuilder的区别浅谈
- 小狼的ACM之旅
- 浅谈java String,StringBuilder,StringBuffer
- java进阶之路
- java进阶之路
- Java进阶之路
- Java进阶之路
- Java进阶之路
- java进阶之路
- java进阶之路
- 448. Find All Numbers Disappeared in an Array
- 简单的JavaWeb投票系统
- 给SwipeRefreshLayout添加上拉加载更多功能
- 高性能MySQL读书笔记:3、服务器性能剖析
- Message中自己new Message()和Message.obtain()的区别?
- 占小狼-Java进阶之路-浅谈StringBuffer
- 【c++】多态总结
- Android中把矩形图片切成圆形图片
- 学习C 的第七天
- Doge学HTML - 1
- springMVC源码分析--SimpleServletHandlerAdapter(二)
- 经典查询练手第三篇
- 《Effective Objective-C 2.0》读书笔记---第七章
- 安装eclipse