一、字符串连接的效率问题

使用String连接字符串时为什么慢?

小知识点

java中对数组进行初始化后,该数组所占的内存空间、数组长度都是不可变的。

创建一个字符串,为字符串对象分配内存空间,会耗费掉一定的时间(CPU)与空间(内存)代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能。

过多无用的中间对象

每次连接字符串时都会创建一个新的String对象,随着拼接次数的增多,这个对象会越来越大。 如,进行100次拼接需要创建100个String对象才能够达到目的。

StringBuilder在连接时为什么效率更高?

字符数组的扩容机制:

private void ensureCapacityInternal(int minimumCapacity) {
// 最小所需容量minimumCapacity是否比原数组长度要长
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
} private int newCapacity(int minCapacity) {
// 计算扩容之后的容量newCapacity
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
// 扩容后还小于所需的最小容量
if (newCapacity - minCapacity < 0) {
// 设置新容量为最小所需容量minimumCapacity
newCapacity = minCapacity;
}
// newCapacity是否溢出,newCapacity是否比数组所能分配的最大容量 MAX_ARRAY_SIZE 还要大。
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
} private int hugeCapacity(int minCapacity) {
// 最小所需容量minCapacity大于Integer.MAX_VALUE时抛出内存溢出异常
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
// 如果minCapacity介于MAX_ARRAY_SIZE和Integer.MAX_VALUE之间,则新的容量为minCapacity,否则直接使用MAX_ARRAY_SIZE作为新的容量。
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}

向原StringBuilder对象中追加字符串时:

1.追加对象str为null时追加'null'字符

2.确认是否需要进行扩容操作

  • 最小所需容量minimumCapacity是否比原数组长度要长,即当原数组长度不能满足所需最小容量时进行扩容操作。
  • 计算扩容之后的容量newCapacity,newCapacity = (value.length * 2) + 2。
  • 扩容后是否还小于所需的最小容量,如果小于则直接设置新容量为最小所需容量minimumCapacity。
  • newCapacity是否溢出,newCapacity是否比数组所能分配的最大容量 MAX_ARRAY_SIZE 还要大。如果是的话则判断,最小所需容量minCapacity大于Integer.MAX_VALUE时抛出内存溢出异常,如果minCapacity介于MAX_ARRAY_SIZE和Integer.MAX_VALUE之间,则新的容量为minCapacity,否则直接使用MAX_ARRAY_SIZE作为新的容量。

3.str.getChars()将str追加到value的末尾

效率高的原因

  1. 扩容机制保证了,只有在满足扩容条件 minimumCapacity - value.length > 0 时才会进行扩容生成新的数组,所以大部分情况都是在对原数组进行操作,避免了产生过多的无用char[]对象,节省了系统资源的开销。

代码

/**
* 比较字符串连接速度
*
* @Author: lingyejun
* @Date: 2019/8/17
* @Describe:
* @Modified By:
*/
public class LinkCompare { /**
* 原始字符串连接
*
* @param times
*/
public static void linkByString(int times) { Long startTime = System.currentTimeMillis(); String initStr = "";
for (int i = 0; i < times; i++) {
initStr = initStr + i;
} Long endTime = System.currentTimeMillis(); System.out.println("String 连接 " + times + " 次 消耗:" + (endTime - startTime) + "ms");
} /**
* 使用StringBuilder连接字符串
*
* @param times
*/
public static void linkByStringBuilder(int times) { Long startTime = System.currentTimeMillis(); StringBuilder initStr = new StringBuilder();
for (int i = 0; i < times; i++) {
initStr.append(i);
} Long endTime = System.currentTimeMillis(); System.out.println("StringBuilder 连接 " + times + " 次 消耗:" + (endTime - startTime) + "ms");
} /**
* 使用StringBuffer连接字符串
*
* @param times
*/
public static void linkByStringBuffer(int times) { Long startTime = System.currentTimeMillis(); StringBuffer initStr = new StringBuffer();
for (int i = 0; i < times; i++) {
initStr.append(i);
} Long endTime = System.currentTimeMillis(); System.out.println("StringBuffer 连接 " + times + " 次 消耗:" + (endTime - startTime) + "ms");
} public static void main(String[] args) { // 100000000
linkByStringBuilder(40000);
//-XX:+PrintGCDetails
//linkByString(40000); }
}

二、StringBuilder和String Buffer的线程安全比较

验证StringBuffer的线程安全性

线程不安全的原因

public StringBuilder append(String str) {
super.append(str);
return this;
} public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}

测试代码

import java.util.ArrayList;
import java.util.List; /**
* StringBuilder和StringBuffer的并发测验
*
* @Author: lingyejun
* @Date: 2019/8/17
* @Describe:
* @Modified By:
*/
public class SecurityCompare { public void stringBuilderTest() { // 初始化StringBuilder
StringBuilder stringBuilder = new StringBuilder(); // joinList
List<StringBuilderThread> joinList = new ArrayList<>(); // 模拟并发场景
for (int i = 0; i < 1000; i++) {
StringBuilderThread sbt = new StringBuilderThread(stringBuilder);
sbt.start();
joinList.add(sbt);
} // 等待append线程执行完毕后再执行主线程
for (StringBuilderThread thread : joinList) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
} // 打印最终的结果
System.out.println("StringBuilder 并发append的结果: " + stringBuilder.length());
} public void stringBufferTest() { // 初始化StringBuffer
StringBuffer stringBuffer = new StringBuffer(); // joinList
List<StringBufferThread> joinList = new ArrayList<>(); // 模拟并发场景
for (int i = 0; i < 1000; i++) {
StringBufferThread sbf = new StringBufferThread(stringBuffer);
sbf.start();
joinList.add(sbf);
} // 等待append线程执行完毕后再执行主线程
for (StringBufferThread thread : joinList) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
} // 打印最终的结果
System.out.println("StringBuffer 并发append的结果: " + stringBuffer.length());
} public static void main(String[] args) { SecurityCompare securityCompare = new SecurityCompare(); securityCompare.stringBuilderTest();
securityCompare.stringBufferTest(); } public static class StringBuilderThread extends Thread { private StringBuilder stringBuilder; public StringBuilderThread(StringBuilder stringBuilder) {
this.stringBuilder = stringBuilder;
} @Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
stringBuilder.append("a");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} }
} private static class StringBufferThread extends Thread { private StringBuffer stringBuffer; public StringBufferThread(StringBuffer stringBuffer) {
this.stringBuffer = stringBuffer;
} @Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
stringBuffer.append("a");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} }
}
}

三、结论

  1. String为固定长度的字符串,StringBuilder和StringBuffer为变长字符串。
  2. StringBuffer是线程安全的,StringBuilder是非线程安全的。
  3. StringBuilder和StringBuffer的默认初始容量是16,可以提前预估好字符串的长度,进一步减少扩容带来的额外开销。

手把手实例对比String、StringBuilder字符串的连接效率及StringBuilder和StringBuffer线程安全的比较的更多相关文章

  1. 6.2 C++ string类型字符串的连接

    参考:http://www.weixueyuan.net/view/6391.html 总结: 对于string类型变量,我们可以直接用“+”或者“+=”进行字符串的连接,操作符非常方便. 用“+”风 ...

  2. 从String类型字符串的比较到StringBuffer和StringBuilder

    1. String类型 String类源码 为了从本质上理解String类型的特性所在,我们从String类型的源码看起,在源码中String类的注释中存在以下: /**Strings are con ...

  3. Java基础 -- 连接字符串时,使用+还是StringBuilder

    结论 1-源代码中使用的+连接,实际上都使用的是StringBuilder. 2-用jad工具反编译,好处之一就是可以同时生成字节码和源代码.这样可以进行对照研究. ----------------- ...

  4. StringBuilder String string.Concat 字符串拼接速度再议

    首先看测试代码: public class StringSpeedTest { "; public string StringAdd(int count) { string str = st ...

  5. StringBuilder String string.Concat 字符串拼接速度

    首先看测试代码: public class StringSpeedTest { "; public string StringAdd(int count) { string str = st ...

  6. .NET面试题解析(03)-string与字符串操作

      系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 字符串可以说是C#开发中最常用的类型了,也是对系统性能影响很关键的类型,熟练掌握字符串的操作非常重要. 常 ...

  7. String.format字符串拼接

    一.String.Format1.简介      String类的format()方法用于创建格式化的字符串以及连接多个字符串对象. 2.参数      format()方法有两种重载形式. form ...

  8. C# 数据类型之 String(字符串)

    Ø  简介 在开发中最常见的数据类型就是 String 类型,即字符串类型.为什么要单独讨论下这个类型,是因为:它是系统内置的基础数据类型:它的使用频率非常高:它是一个特殊的引用类型.其实大家都会使用 ...

  9. String拼接字符串效率低,你知道原因吗?

    面试官Q1:请问为什么String用"+"拼接字符串效率低下,最好能从JVM角度谈谈吗? 对于这个问题,我们先来看看如下代码: public class StringTest { ...

随机推荐

  1. HashMap的内部结构与hash冲突

    HashMap的内部结构 HashMap简介: HashMap继承AbstractMap,AbstractMap实现Map接口 HashMap是线程不同步的,线程不安全的 HashMap可以把null ...

  2. js获取列表多条数据(接口)

    读取数据://ajax去服务器端校验 $.ajax({ type:"post", url:"http://", data:{deviceid:1}, dataT ...

  3. 常用模块 - configparse模块

    一.简介 configparser模块在Python中是用来读取配置文件的,配置文件的格式跟windows下的ini配置文件相似,可以包含一个或多个节点(section),每个节可以有多个参数(键=值 ...

  4. Java框架之MyBatis框架(一)

    一.框架介绍: MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动.创建connection.创建sta ...

  5. 解决IDEA Java Web项目没问题,但部署时出错的问题

    如果确定代码没问题,那多半是项目中用到的库没有被Tomcat复制到部署位置的lib目录下. 点击调试/运行,看到控制台Tomcat在部署,但一直不弹出浏览器页面,Tomcat控制台报错如下: 是在Ar ...

  6. SAP Hybris Discount group,折扣组,折扣记录,用户组几组概念的关联

    在backoffice的Price Settings->Discount->Customer Discount Groups菜单下面,创建一个新的Customer Discount Gro ...

  7. java实现mysql数据备份

    /** * @param hostIP ip地址,可以是本机也可以是远程 * @param userName 数据库的用户名 * @param password 数据库的密码 * @param sav ...

  8. 8 个 Python 实用脚本,赶紧收藏备用!

    脚本写的好,下班下得早!程序员的日常工作除了编写程序代码,还不可避免地需要处理相关的测试和验证工作. 例如,访问某个网站一直不通,需要确定此地址是否可访问,服务器返回什么,进而确定问题在于什么.完成这 ...

  9. Windows定时任务下载linux服务器批量文件到本地

    编写批文件 1.1 编写main.bat文件 E: cd logs ftp -n -s:"E:\logs\mget.bat" 1.2 编写mget.bat文件 open ip地址 ...

  10. 关于 ES5 & ES6 数组遍历的方法

    ES5 数组遍历方法 1.for 循环 , , , , ] ; i < arr.length; i++) { console.log(arr[i]) } 2.forEach , , , , ] ...