引言
最近我的同事分享了一个页面渲染过程中,字符串拼接的优化处理。我们系统的页面渲染是分模块渲染,每个模块渲染完成后都是一个String型的html片段,最终我们需要把所有模块的html片段拼接成一个完整html页面。老代码逻辑大致如下:
List<String> moudleHtmls = new ArrayList<>(moduleSize); for(int i=0;i<moduleSize;i++){ String tempHtml = null; //模块内容渲染 省略代码 moudleHtmls.set(i, tempHtml);//把每个模块html片段放入List } String pageString=""; for (int i=0;i <moduleSize; i++){ pageString+=moudleHtmls.get(i);//遍历把所有模块的html片段拼接成一个页面 }
通过反编译发现第二个for循环,每次拼接操作都会自动生成一个StringBuilder对象,反编译的代码如下(第二个for循环部分):
String pageString=""; for (int i=0;i <moduleSize; i++){ pageString=new StringBuilder().append(pageString).append(moudleHtmls.get(i)).toString(); }
每次for循环都会生成一个StringBuilder,其默认容量为16,超过16又需要自动扩容(后面源码详细讲解),势必会影响性能。改进后的代码如下:
StringBuilder pageString = new StringBuilder(pageTemplate.length * 2);//预估页面长度 for (int i=0;i <moduleSize; i++){ pageString.append(moudleHtmls.get(i));//遍历把所有模块的html片段拼接成一个页面 }
通过对比发现改造后的拼接性能大幅提升。我们系统每天都有上百万次的页面渲染,这个小小的改动带来的收益是可想而知的。
为了对StringBuilder有更深入的了解,决定阅读其相关源码,做一次全面的总结。
字符串连接的三种方式
1、字符串连接操作符(+),是把多个字符串合并为一个字符串的便利途径。
2、 StringBuilder,创建该类对象,调用其append方法实现字符串连接,从jdk1.5开始支持。
3、StringBuffer,用法和StringBuilder相同,从jdk1.0就开始支持。
通过阅读源码发现StringBuilder、StringBuffer都是继承自AbstractStringBuilder,方式实现也是基本相同,只是StringBuffer的每个方式都是synchronized修饰的,也就是说StringBuffer适合使用在多线程并发环境下的字符串拼接。单线程环境下使用StringBuilder的性能会更好些。
String t=“123”+“456”;
这种方式每次执行,本质上是创建一个新String,然后把两个String的的内容复制到这个新String,性能非常差。但从jdk1.5开始,该操作做了优化,执行过程中会自动new 一个StringBuilder,真实的执行过程变为:String t=new StringBuilder().append(“123”).append(“456”)。
也就是说在拼接后的字符串总长度比较短的情况下(总长度不超过16),直接使用“(+)”符号拼接是最佳选择。
如果拼接后的字符串总长度大于16,最好新建一个指定预估容量的StringBuilder,调用其append方法进行拼接。如上述优化中:
StringBuilder pageString = new StringBuilder(pageTemplate.length * 2);//预估页面长度
另外如果在for循环中也不建议直接使用(+)操作,因为这会导致每次循环都会新创建一个StringBuilder,如上述优化中主要就是优化这个问题。
为什么要指定StringBuilder容量,其实跟ArrayList、HashMap等原理相同,都存在自动扩容的问题,看下源码就一目了然。
StringBuilder、StringBuffer、AbstractStringBuilder 源码解析
StringBuilder、StringBuffer都是继承自AbstractStringBuilder抽象类,每个方法的实现基本相同,都是调用AbstractStringBuilder中的方法。唯一的差异,就是toString方法,方法也是AbstractStringBuilder中唯一的抽象方法。
成员变量是一个char性的数组,StringBuilder、StringBuffer的所有操作基本都是围绕这个数组进行:
char[] value;//在AbstractStringBuilder中定义
该字符数组在StringBuilder、StringBuffer的初始默认容量都是16,方法操作完全相同,以StringBuilder为例:
//默认构造方法 public StringBuilder() { super(16);//默认容量 } //指定容量构造方法 public StringBuilder(int capacity) { super(capacity); } //指定初始字符串构造方法 public StringBuilder (String str) { super(str.length() + 16); //初始容量为字符串长度+16 append(str); }
这里通过super调用AbstractStringBuilder的构造方法:
AbstractStringBuilder(int capacity) { value = new char[capacity];//指定数组容量,初始化字节数组 }
成员变量字节数组value初始化完成。
append系列重载方法
AbstractStringBuilder中的append系列重载方法(StringBuilder、StringBuffer 中的append方法通过super直接调用该系列方法),是其核心方法,可以处理处理所有的基础类型(比如boolean、int、long、double等)、引用类型的拼接(String、Object、AbstractStringBuilder自身等),这里以用得最多的append(String)进行讲解:
public AbstractStringBuilder append(String str) { if (str == null) return appendNull();//注意如果为str为null,会拼接一个"null"字符串 int len = str.length(); ensureCapacityInternal(count + len);//判断容量是否足够,如不够先扩容,再copy到新扩容后的数组 str.getChars(0, len, value, count);//把str copy到 字符数组中 count += len;//重新计算字符数组总长度 return this; } private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) expandCapacity(minimumCapacity);//容量不够,进行扩容 } void expandCapacity(int minimumCapacity) { int newCapacity = value.length * 2 + 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); //把老数据复制到扩容后的新数组。 }
StringBuilder、StringBuffer的字符串拼接,实际上就是把待拼接的字符串,放到自己的字符数组,如果字符数组的容量不够,需要进行扩容。具体操作是新创建一个字符数组(容量为老数组的2倍+2),再把老数组中的内容copy到新数组。这就是为什么在拼接大量字符串(拼接后超过长度16),最好采用指定容量的方式创建StringBuilder(或StringBuffer),防止拼接过程中不断扩容带来的性能消耗。
toString方法
需要注意的是StringBuilder、StringBuffer不是String类型,不能强制转换。只能通过调用其toString方法转换为String。前面已经提到StringBuilder、StringBuffer重要区别就是toString方法的实现不同。
StringBuilder的toString方法,采用自己成员变量value字符数组新建一个String:
@Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
StringBuffer的toString方法:
public synchronized String toString() { if (toStringCache == null) { toStringCache = Arrays.copyOfRange(value, 0, count);//缓存成员变量value字符数组 } return new String(toStringCache, true); }
其中toStringCache主要用作缓存。当StringBuffer对成员变量value字符数组有修改时,需要先清理缓存,如StringBuffer的 append(String)方法实现:
@Override public synchronized StringBuffer append(String str) { toStringCache = null;//清理缓存 super.append(str);//调用父类AbstractStringBuilder的append方法 return this; }
再来看下StringBuilder的append(String)方法实现:
@Override public StringBuilder append(String str) { super.append(str); return this; //调用父类AbstractStringBuilder的append方法 }
区别有两点:
1、StringBuffer的方法是synchronized修饰。
2、StringBuffer的方法需要清理缓存。
StringBuilder、StringBuffer的append系列方法不再一一讲解,具体操作都差不多,把待拼接类型转换中字符,依次放到其成员变量value字符数组中。
其他方法:
AbstractStringBuilder的delete方法,本质上也是对字符数组的copy操作:
public AbstractStringBuilder delete(int start, int end) { if (start < 0) throw new StringIndexOutOfBoundsException(start); if (end > count) end = count; if (start > end) throw new StringIndexOutOfBoundsException(); int len = end - start; if (len > 0) { System.arraycopy(value, start+len, value, start, count-end);//本质上先复制到一个临时数组,再覆盖原数组 无内存泄漏问题 count -= len; } return this; }
AbstractStringBuilder的insert系列方法,这里以String为例:
public AbstractStringBuilder insert(int offset, String str) { if ((offset < 0) || (offset > length())) throw new StringIndexOutOfBoundsException(offset); if (str == null) str = "null"; int len = str.length(); ensureCapacityInternal(count + len);//确认容量 System.arraycopy(value, offset, value, offset + len, count - offset);//通过复制预留出待插入空间 str.getChars(value, offset);//copy字符串 到指定开始位置 count += len; return this; }
举个列子:在“hello!”字符串中插入“pig”字符串,形成“hellopig!”, java代码如下:
StringBuilder sb = new StringBuilder; sb.append(“hello!”) sb.insert(4,“pig”);
对应的示意图:
AbstractStringBuilder的reverse方法,本质上就是把字符数组里每个字符反序排列,不在累述。
最后总结下:
1、拼接完成的字符串长度短的情况下,直接使用(+)操作符即可:如String t = “123”+”456”。
2、不要在for循环中使用(+)操作符,使用StringBuilder代替。
3、单线程环境下,使用StringBuilder拼接一个比较长的字符串,最好先预估容量,采用指定容量的StringBuilder构造方法,构造StringBuilder实例。
4、多线程环境下使用StringBuffer。
相关推荐
毕业设计&课设_CUMT 信息安全专业毕业设计:基于区块链的能源交易系统,含架构、部署等多方面详细介绍.zip
用Python分析文本数据项目
基于SpringBoot的养老院管理系统源码数据库文档.zip
阿香婆软件强制卸载软件 Ashampoo UnInstaller 是一款强大的卸载工具,彻底、智能著称,如果您选择使用Ashampoo安装程序,它会自动分析软件从开始安装到结束的全过程,并为该软件建立一个日志,以便以后更彻底的卸载它,Ashampoo UnInstaller 全面提速!程序采用了新的技术,速度得到了全面提高。Windows 默认应用现在也可以像其它无用程序和恶意软件一样被无忧卸载。摆脱工具栏、插件和其它隐藏的追踪软件,享受安全快捷的上网体验!新的快照比较功能,可以非常简单的创建安装日志,这是旧版本 UnInstaller 粉丝最急需的!新的界面,便捷的批量卸载,以及内置的在线搜索,Ashampoo UnInstaller 一定会让你惊叹无比。快来体验更清晰、更详尽、更快速的 Ashampoo UnInstaller !Ashampoo UnInstallerAshampoo UnInstaller彻底删除无用程序的所有痕迹! 让安装、试用、卸载程序更加轻松。只需点几下,无用的软件、浏览器扩展和工具栏就能从你的硬盘中
基于java的公交车信息管理系统开题报告
基于java的网络书店系统的开题报告
forge-1.20.1-47.3.0-installer
python
基于springboot云平台的信息安全攻防实训平台源码数据库文档.zip
基于SpringBoot的房屋租赁系统源码数据库文档.zip
毕业设计&课设_家居物联网毕业设计项目:利用多种设备实现,含架构、目标.zip
基于springboot的非学勿扰学习交流平台源码数据库文档.zip
内容概要:本文详细介绍了网络安全领域的基础知识和高级概念,涵盖了网络安全概述、网络架构与协议、操作系统安全、网络攻击与防御、密码学与安全协议、身份认证与访问控制、系统漏洞与补丁管理、网络安全实践及发展趋势与挑战等内容。 适合人群:信息安全专业人员、IT运维人员、网络安全工程师、系统管理员。 使用场景及目标:本文不仅帮助读者理解网络安全的理论知识,还能指导实际操作,适用于日常网络安全管理和应急响应。目标在于增强个人和企业对网络安全的认识,提升防护能力。 其他说明:网络安全是一个不断发展变化的领域,文中提到的内容和技术应及时跟进最新的发展动态,确保网络安全的有效性。
环境说明:开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7 数据库工具:Navicat 开发软件:eclipse/myeclipse/idea Maven包:Maven 浏览器:谷歌浏览器。 项目经过测试均可完美运行
微信小程序是一种轻量级的应用开发平台,主要针对移动端,由腾讯公司推出,旨在提供便捷的、无需下载安装即可使用的应用服务。"京东白条"是京东金融推出的一种信用消费产品,用户可以在京东商城购物时享受先消费后付款的便利。这款"微信小程序京东白条小程序源码"包含了实现这一功能的小程序源代码,以及相应的导入教程,对于开发者来说,是一个有价值的参考资料。 源码是程序开发的基础,它包含了开发者编写的控制程序行为的指令。在这个案例中,源码可能包括了与京东白条接口交互的逻辑,用户界面设计,以及数据处理等功能。通过分析这些源码,开发者可以学习如何集成京东的API,实现白条的查询、支付和管理等操作。 "源码导入视频教程"和"源码导入文档教程"是辅助开发者理解和使用源码的重要资料。视频教程通常以直观的方式展示源码的导入步骤和环境配置,帮助开发者快速上手。文档教程则可能包含了详细的步骤说明,解决常见问题的技巧,以及开发过程中的注意事项,对于初学者来说尤其有价值。 微信小程序的开发涉及到以下几个关键技术点: 1. **WXML(Weixin Markup Language)**:这是微信小程序的视图层语言,
市大脑的基础平台是支撑其高效运行的关键。该平台通过整合电子政务网、视联网、互联网等多网资源,以及云计算、大数据、人工智能等先进技术,打造了一个统一、开放、可扩展的底层架构。在这个基础上,城市大脑能够接入海量数据,包括但不限于视频监控、交通流量、环境监测、民生服务等领域的实时数据,并进行高效处理和分析。这些数据经过清洗、转换、开发后,形成了丰富的数据资源池,为城市治理提供了坚实的数据支撑。 在数据资源的基础上,智慧城市大脑进一步构建了算法服务平台和融合业务应用系统。算法服务平台集成了多种先进的视频分析算法和人工智能算法,如人脸识别、车辆识别、城市问题智能识别等,这些算法能够实时分析视频流和数据流,快速识别城市运行中的问题和异常。同时,融合业务应用系统则将这些算法与城市治理的各个领域相结合,如交通、环保、旅游、医疗等,形成了多个具有实战价值的应用场景。例如,在交通领域,城市大脑能够通过实时分析交通流量和路况信息,优化信号灯控制策略,缓解交通拥堵;在环保领域,则能够实时监测空气质量和水质情况,及时预警环境污染事件。 数字驾驶舱是智慧城市大脑的又一亮点。它作为城市治理能力现代化的新抓手,实现了数据一屏展示、指标一屏分析、指挥一屏联动、场景一屏闭环和治理一屏透视。通过数字驾驶舱,决策者可以直观地看到城市运行的全貌,及时获取关键信息,做出准确的判断和决策。同时,数字驾驶舱还能够根据用户的需求进行个性化配置,提供多种数据处理和分析工具,帮助用户深入挖掘数据价值,提升城市治理的精细化和智能化水平。 智慧城市大脑的建设成效显著。它不仅提高了城市治理的效率和准确性,还带来了显著的社会效益和经济效益。通过优化资源配置、降低运营成本、提升服务质量等方式,智慧城市大脑为城市居民提供了更加便捷、高效、舒适的生活环境。同时,它也为城市的可持续发展注入了新的动力,推动了产业升级和经济转型。可以说,智慧城市大脑已经成为了未来城市发展的重要方向和趋势,它将引领我们走向一个更加智慧、绿色、宜居的城市新时代。 总的来说,智慧城市大脑是一个集数据、算法、应用为一体的综合性解决方案,它通过高度集成和智能分析,实现了城市治理的精细化和智能化。在未来的发展中,随着技术的不断进步和应用场景的不断拓展,智慧城市大脑将会发挥出更加巨大的潜力和价值,为城市的可持续发展和社会的全面进步做出更大的贡献。
毕业设计&课设_景区购票系统(含更新情况、数据字典及多种语言实现).zip
上传一个【考研冲刺】资源
C0858 手机之家(1页).Zip