StringBuilder_学习笔记
参考:https://www.jianshu.com/p/160c9be0b132
连接符号 "+" 本质
字符串变量(非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数据进行复制;
在使用StringBuilder时,如果给定一个合适的初始值,可以避免由于char[]数组多次复制而导致的性能问题。
不同初始容量的性能测试:
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);
}
String的构造方法中,会新建一个大小相等的char数组,并使用 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方案。
StringBuilder_学习笔记的更多相关文章
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
- PHP-自定义模板-学习笔记
1. 开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2. 整体架构图 ...
- PHP-会员登录与注册例子解析-学习笔记
1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...
- 2014年暑假c#学习笔记目录
2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...
- JAVA GUI编程学习笔记目录
2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...
- seaJs学习笔记2 – seaJs组建库的使用
原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...
- CSS学习笔记
CSS学习笔记 2016年12月15日整理 CSS基础 Chapter1 在console输入escape("宋体") ENTER 就会出现unicode编码 显示"%u ...
- HTML学习笔记
HTML学习笔记 2016年12月15日整理 Chapter1 URL(scheme://host.domain:port/path/filename) scheme: 定义因特网服务的类型,常见的为 ...
- DirectX Graphics Infrastructure(DXGI):最佳范例 学习笔记
今天要学习的这篇文章写的算是比较早的了,大概在DX11时代就写好了,当时龙书11版看得很潦草,并没有注意这篇文章,现在看12,觉得是跳不过去的一篇文章,地址如下: https://msdn.micro ...
随机推荐
- data-ng-hide指令用于隐藏或显示HTML元素
<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content=&q ...
- 使用C#的新特性:可空类型
随着C#语言最新标准的出炉,现在它也提供了对可空类型的支持.这个小变化将会在处理那些包括可选项的数据库记录时非常有用.当然在其他地方,它也是非常有用的. 简单说来,可空数据类型就是包含了所定义的数据类 ...
- VCTransitionsLibrary –自定义iOS交互式转场动画的库
简介 VCTransitionsLibrary 提供了许多适用于入栈,出栈,模态等场景下控制器切换时的转场动画.它本身提供了一个定义好的转场动画库,你可以拖到自己工程中直接使用;也提供了许多拥有不同转 ...
- margin与padding大比拼
用margin还是用padding这个问题相信是每个学css的人都想要去深入了解的. CSS边距属性定义元素周围的空间.通过使用单独的属性,可以对上.右.下.左的外边距进行设置.也可以使用简写的外边距 ...
- [转]C++ Template
引言 模板(Template)指C++程序设计设计语言中采用类型作为参数的程序设计,支持通用程序设计.C++ 的标准库提供许多有用的函数大多结合了模板的观念,如STL以及IO Stream. 函数模板 ...
- vue数据绑定html
html标签的纯文本显示/被当做html标签处理: 1)使用两个大括号时,假如字符串内容是html标签,那么不会被转义: 2)使用三个大括号时,字符串内的html标签会被直接转义 a.两个大括号: & ...
- GMT 时间格式转换到 TDateTime (Delphi)
//GMT 时间格式转换到 TDateTime //忽略时区 function GMT2DateTime(const pSour:PAnsiChar):TDateTime; function GetM ...
- Python必学:使用哪款文本编辑器更好?
Python的交互式命令行写程序,好处是一下就能得到结果,坏处是没法保存,下次还想运行的时候,还得再敲一遍. 所以,实际开发的时候,我们总是使用一个文本编辑器来写代码,写完了,保存为一个文件,这样,程 ...
- 779. K-th Symbol in Grammar
class Solution { public: int kthGrammar(int N, int K) { return helper(N, K, false); } int helper(int ...
- 最小生成数kruskal算法和prim算法
定义 连通图:在无向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该无向图为连通图. 强连通图:在有向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该有向图为强连通图. 连通网:在 ...