关于JDK8中的字符串拼接示例详解

前言

在Java开发者中,字符串的拼接占用资源高往往是热议的话题.

让我们深入讨论一下为什么会占用高资源。

在Java中,字符串对象是不可变的,意思是它一旦创建,你就无法再改变它。所以在我们拼接字符串的时候,创建了一个新的字符串,旧的被垃圾回收器所标记。

如果我们处理上百万的字符串,然后,我们就会生成百万的额外字符串被垃圾回收器处理。

在大多数的教程中,也许你会看到用+号拼接字符串会生成多个String,导致性能过差,建议使用StringBuffer/StringBuilder来拼接。

可是真的是这样的吗?

本文在JDK8中做了如下实验:

public static void main(String[] args) {
 String result = "";
 result += "some more data";
 System.out.println(result);
 }

通过javap -c来反编译得到:

Code:
 0: aload_0  // Push 'this' on to the stack
 1: invokespecial #1 // Invoke Object class constructor
    // pop 'this' ref from the stack
 4: return  // Return from constructor

 public static void main(java.lang.String[]);
 Code:
 0: ldc  #2 // Load constant #2 on to the stack
 2: astore_1  // Create local var from stack (pop #2)
 3: new  #3 // Push new StringBuilder ref on stack
 6: dup  // Duplicate value on top of the stack
 7: invokespecial #4 // Invoke StringBuilder constructor
    // pop object reference
 10: aload_1  // Push local variable containing #2
 11: invokevirtual #5 // Invoke method StringBuilder.append()
    // pop obj reference + parameter
    // push result (StringBuilder ref)
 14: ldc  #6 // Push "some more data" on the stack
 16: invokevirtual #5 // Invoke StringBuilder.append
    // pop twice, push result
 19: invokevirtual #7 // Invoke StringBuilder.toString:();
 22: astore_1  // Create local var from stack (pop #6)
 23: getstatic #8 // Push value System.out:PrintStream
 26: aload_1  // Push local variable containing #6
 27: invokevirtual #9 // Invoke method PrintStream.println()
    // pop twice (object ref + parameter)
 30: return  // Return void from method

可以看到Java编译器优化了生成的字节码,自动创建了一个StringBuilder,并进行append操作。

由于构建最终字符串的子字符串在编译时已经已知了,在这种情况下Java编译器才会进行如上的优化。这种优化称为a static string concatenation optimization,自JDK5时就开始启用。

那是否就能说明在JDK5以后,我们不再需要手动生成StringBuilder,通过+号也能达到同样的性能?

我们尝试下动态拼接字符串:

动态拼接字符串指的是仅在运行时才知道最终字符串的子字符串。比如在循环中增加字符串:

public static void main(String[] args) {
 String result = "";
 for (int i = 0; i < 10; i++) {
  result += "some more data";
 }
 System.out.println(result);
 }

同样反编译:

Code:
 0: aload_0  // Push 'this' on to the stack
 1: invokespecial #1 // Invoke Object class constructor
    // pop 'this' ref from the stack
 4: return  // Return from constructor

 public static void main(java.lang.String[]);
 Code:
 0: ldc  #2 // Load constant #2 on to the stack
 2: astore_1  // Create local var from stack, pop #2
 3: iconst_0  // Push value 0 onto the stack
 4: istore_2  // Pop value and store it in local var
 5: iload_2  // Push local var 2 on to the stack
 6: i2d  // Convert int to double on
    // top of stack (pop + push)
 7: ldc2_w  #3 // Push constant 10e6 on to the stack
 10: dcmpg  // Compare two doubles on top of stack
    // pop twice, push result: -1, 0 or 1
 11: ifge  40 // if value on top of stack is greater
    // than or equal to 0 (pop once)
    // branch to instruction at code 40
 14: new  #5 // Push new StringBuilder ref on stack
 17: dup  // Duplicate value on top of the stack
 18: invokespecial #6 // Invoke StringBuilder constructor
    // pop object reference
 21: aload_1  // Push local var 1 (empty String)
    // on to the stack
 22: invokevirtual #7 // Invoke StringBuilder.append
    // pop obj ref + param, push result
 25: ldc  #8 // Push "some more data" on the stack
 27: invokevirtual #7 // Invoke StringBuilder.append
    // pop obj ref + param, push result
 30: invokevirtual #9 // Invoke StringBuilder.toString
    // pop object reference
 33: astore_1  // Create local var from stack (pop)
 34: iinc  2, 1 // Increment local variable 2 by 1
 37: goto  5 // Move to instruction at code 5
 40: getstatic #10 // Push value System.out:PrintStream
 43: aload_1  // Push local var 1 (result String)
 44: invokevirtual #11 // Invoke method PrintStream.println()
    // pop twice (object ref + parameter)
 47: return  // Return void from method

可以看到在14的时候new了StringBuilder,但是在37的时候goto到了5,在循环过程中,并没有达到最优化,不断在生成新的StringBuilder。

所以上述代码类似:

String result = "";
for (int i = 0; i < 10; i++) {
 StringBuilder tmp = new StringBuilder();
 tmp.append(result);
 tmp.append("some more data");
 result = tmp.toString();
}
System.out.println(result);

可以看到不断生成新的StringBuilder,并且通过tostring,原来的StringBuilder将不再引用,作为垃圾,也增加了GC成本。

所以,在实际的使用中,当你无法区分字符串是静态拼接还是动态拼接的时候,还是使用StringBuilder吧。

Reference:

http://www.pellegrino.link/2015/08/22/string-concatenation-with-java-8.html

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

您可能感兴趣的文章:

  • CentOS 7下JDK8的详细安装步骤
  • JDK8中新增的原子性操作类LongAdder详解
  • 基于JDK8总结java中的interrupt
  • Java新手环境搭建 JDK8安装配置教程
  • jdk8的datetime时间函数使用示例
  • Java 高并发十: JDK8对并发的新支持详解
  • Ubuntu 安装 JDK8 的两种方法(总结)
  • 深入浅析JDK8新特性之Lambda表达式
(0)

相关推荐

  • Java新手环境搭建 JDK8安装配置教程

    最近有时间,写一些很简单.很基础的东西,主要在操作层面.主要考虑如下: 1.经常搭建开发环境,所以有必要记录一下,自己也可以备查: 2.给新手看,写一些最最简单实用的东西. 1.确认 Java 没有安装过 首先先确认我们的电脑上没有安装 Java,打开命令行,输入 java -version,看到如下显示就说明 Java 还没有被安装. 2.在 Java 没有安装过,到官网下载 Java 最新版本 接下来到 Oracle 的网站上下载 Java 最新版本.百度搜索关键字"oradle java&

  • Java 高并发十: JDK8对并发的新支持详解

    1. LongAdder 和AtomicLong类似的使用方式,但是性能比AtomicLong更好. LongAdder与AtomicLong都是使用了原子操作来提高性能.但是LongAdder在AtomicLong的基础上进行了热点分离,热点分离类似于有锁操作中的减小锁粒度,将一个锁分离成若干个锁来提高性能.在无锁中,也可以用类似的方式来增加CAS的成功率,从而提高性能. LongAdder原理图: AtomicLong的实现方式是内部有个value 变量,当多线程并发自增,自减时,均通过CA

  • 基于JDK8总结java中的interrupt

    1. interrupt知识点 以下总结基于JDK8 本文不会完整说明interrupt,只会罗列一些比较重要的点.完整了解Thread.interrupt可以看参考资料. 以下的一些理解新的有助于理解参考资料的文章: interrupt方法调用后,针对BLOCKED状态的线程,只是设定中断标志位为true.是否响应中断(感知这个标志位的变化)取决于API的设计.JDK的阻塞IO API.Synchronized同步块.还有Lock中的很多方法(不包括lockInterruptibly)都是不响

  • Ubuntu 安装 JDK8 的两种方法(总结)

    ubuntu 安装jdk 的两种方法总结: 1:通过ppa(源) 方式安装. 2:通过官网下载安装包安装. 这里推荐第1种,因为可以通过 apt-get upgrade 方式方便获得jdk的升级 使用ppa/源方式安装 1.添加ppa sudo add-apt-repository ppa:webupd8team/java sudo apt-get update 2.安装oracle-java-installer jdk8 sudo apt-get install oracle-java8-in

  • 深入浅析JDK8新特性之Lambda表达式

    第一次是接触Lambda表达式是在TypeScript中(JavaScript的超集中),当时是为了让TypeScript的this方法外而不是本方法内所使用的.使用过后突然想到Lambda不是JDK8的重量级新特性么?于是感觉查阅相关资料并记录下来: 一. 行为参数化 行为参数化简单的说就是函数的主体仅包含模板类通用代码,而一些会随着业务场景而变化的逻辑则以参数的形式传递到函数之中,采用行为参数化可以让程序更加的通用,以应对频繁变更的需求. 考虑一个业务场景,假设我们需要通过程序对苹果进行筛选

  • CentOS 7下JDK8的详细安装步骤

    本文为大家分享了CentOS 7下安装JDK8的详细步骤,供大家参考,具体内容如下 一.下载JDK 至oracle官网下载,如图所示 二.安装JDK 把rpm文件拷贝至/usr/java,如果没有java文件夹, mkdir /usr/java/ 使用npm -ivh jdk文件名安装,如图所示 三.配置环境变量 使用命令 vi /etc/profile,插入图中红色部分: 注意=两边不要有空格 使用:wq保存退出,然后输入source /etc/profile和java -version,如图

  • JDK8中新增的原子性操作类LongAdder详解

    前言 本文主要给大家介绍了关于JDK8新增的原子性操作类LongAdder的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: LongAdder简单介绍 LongAdder类似于AtomicLong是原子性递增或者递减类,AtomicLong已经通过CAS提供了非阻塞的原子性操作,相比使用阻塞算法的同步器来说性能已经很好了,但是JDK开发组并不满足,因为在非常高的并发请求下AtomicLong的性能不能让他们接受,虽然AtomicLong使用CAS但是CAS失败后还是通过

  • jdk8的datetime时间函数使用示例

    JDK8已发布,写了一个datetime时间函数使用方法的小示例 复制代码 代码如下: package datetime; import static java.time.temporal.TemporalAdjusters.lastDayOfMonth;import static java.time.temporal.TemporalAdjusters.previousOrSame; import java.time.DayOfWeek;import java.time.Duration;im

  • 关于JDK8中的字符串拼接示例详解

    前言 在Java开发者中,字符串的拼接占用资源高往往是热议的话题. 让我们深入讨论一下为什么会占用高资源. 在Java中,字符串对象是不可变的,意思是它一旦创建,你就无法再改变它.所以在我们拼接字符串的时候,创建了一个新的字符串,旧的被垃圾回收器所标记. 如果我们处理上百万的字符串,然后,我们就会生成百万的额外字符串被垃圾回收器处理. 在大多数的教程中,也许你会看到用+号拼接字符串会生成多个String,导致性能过差,建议使用StringBuffer/StringBuilder来拼接. 可是真的

  • Go语言字符串基础示例详解

    目录 包含 Contains(s,substr string) bool ContainsAny(s, chars string) bool 统计 Count(s, substr string) int 比较 EqualFold(s, t string) bool HasPrefix(s, prefix string) bool HasSuffix(s, suffix string) bool 分割 Fields(s string) []string 下标 Index(s, sep string

  • Go Java 算法之字符串解码示例详解

    目录 字符串解码 方法一:栈(Java) 方法二:递归(Go) 字符串解码 给定一个经过编码的字符串,返回它解码后的字符串. 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正重复 k 次.注意 k 保证为正整数. 你可以认为输入字符串总是有效的:输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的. 此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入. 示例 1: 输入:

  • Cython处理C字符串的示例详解

    目录 楔子 创建 C 字符串 引用计数陷阱 strlen strcpy strcat strcmp sprintf 动态申请字符串内存 memset memcpy memmove memcmp 小结 楔子 在介绍数据类型的时候我们说过,Python 的数据类型相比 C 来说要更加的通用,但速度却远不及 C.如果你在使用 Cython 加速 Python 时遇到了瓶颈,但还希望更进一步,那么可以考虑将数据的类型替换成 C 的类型,特别是那些频繁出现的数据,比如整数.浮点数.字符串. 由于整数和浮点

  • C语言中的正则表达式使用示例详解

    正则表达式,又称正规表示法.常规表示法(英语:Regular Expression,在代码中常简写为regex.regexp或RE).正则表达式是使用单个字符串来描述.匹配一系列符合某个句法规则的字符串. 在c语言中,用regcomp.regexec.regfree 和regerror处理正则表达式.处理正则表达式分三步: 编译正则表达式,regcomp: 匹配正则表达式,regexec: 释放正则表达式,regfree. 函数原型 /* 函数说明:Regcomp将正则表达式字符串regex编译

  • Flutter 中 Dart的Mixin示例详解

    原文在这里.写的不错,推荐各位看原文. 这里补充一下Mixin的定义: 只要一个类是继承自Object的而且没有定义构造方法,那么这个类可以是一个Mixin了.当然,如果你想让mixin的定义更加的清晰,可以使用mixin关键字开头来定义.具体请参考这里 原文截图体会一下风格. 正文 在经典的面向对象编程语言里一定会有常规的类,抽象类和接口.当然,Dart也有它自己的接口,不过那是另外的文章要说的.有的时候阴影里潜伏者另外的野兽:Mixin!这是做什么的,如何使用?我们来一起发现. 没有mixi

  • shrio中hashedCredentialsMatcher密码匹配示例详解

    类图如下: SimpleCredentialsMatcher是明文匹配,也是shrio框架默认的比对方式,网上的例子多是此方式.实际项目中,数据库中的密码一般是密文,此时密码的匹配需使用HashedCredentialsMatcher完成. 处理过程 在controller中通过Subject的login(token)将接收过来用户账号和密码(明文)交给shrio框架,示例代码如下 其次通过HashedCredentialsMatcher告诉shrio使用加密方式: 最后通过Authorizin

  • Java中的反射机制示例详解

    目录 反射 什么是Class类 获取Class实例的三种方式 通过反射创建类对象 通过反射获取类属性.方法.构造器 更改访问权限和实例赋值 运用场景 反射 反射就是把Java类中的各个成分映射成一个个的Java对象.即在运行状态中,对于任意一个类,都能够知道这个类的所以属性和方法:对于任意一个对象,都能调用它的任意一个方法和属性.这种动态获取信息及动态调用对象方法的功能叫Java的反射机制 每一个Java程序执行必须通过编译.加载.链接和初始化四个阶段 1.编译:将.java.文件编译成字节码.

  • Python中图像算术运算的示例详解

    目录 介绍 算术运算:图像相加 算术运算:图像减法 位运算 介绍 还记得你在小学时学习如何加减数字吗?现在,你也可以对图像做同样的事情! 输入图像可以进行算术运算,例如加法.减法和按位运算(AND.OR.NOT.XOR).这些操作可以帮助提高输入照片的质量. 在本文中,你将了解使用 OpenCV Python 包对图像执行算术和按位运算的步骤.让我们开始吧! 对图像进行算术运算是什么意思? 因此,假设我们希望合并两张单独的照片中的两个像素.我们怎样才能将它们合并? 让我们想象以下场景.第一个像素

  • JavaScript中事件委托的示例详解

    目录 事件流 事件委托 结尾 大家好,我是前端西瓜哥.今天我们来认识一下事件委托. 所谓事件委托,就是将原本应该在当前元素绑定的事件,放到它的祖先元素上,让祖先元素来委托处理. 事件流 事件流指从页面中接收事件的顺序,也可理解为事件在页面中传播的顺序. 事件流由两阶段组成: 捕获事件 冒泡事件 我们通常用 addEventListener 给元素添加事件: document.querySelector('#card')addEventListener( 'click', function (ev

随机推荐