Java代码中4种字符串拼接方式分析

目录
  • 结论
  • 最佳实践
  • 分析过程
    • 环境
    • 分析用示例代码:
    • 代码及结果分析

本文研讨的字符串拼接方式为以下4种:“+”号、StringBuilder、StringJoiner、String#join,对比分析及探讨最佳实践。

结论

后面内容比较枯燥,所以先说结论:

  • 本文研讨的字符串拼接方式为以下4种:“+”号、StringBuilder、StringJoiner、String#join
  • 在简单的字符串拼接场景中「如:"a" + "b" + "c"」,以上四种方式性能无明显差异。
  • 在循环字符串拼接的场景下,使用“+”号性能最低,其他三种方式性能也无明显差异,但是根据验证结果可粗浅发现,指定初始容量的StringBuilder效率最高。当然不光考虑性能,也要考虑垃圾回收效率的问题,避免OOM。
  • 本文最后补充对比了StringBuffer,在无争抢共享资源的场景下,StringBuffer性能并未明显变差。

最佳实践

  • 阿里巴巴Java开发手册-日志规约「5」可进行优化:使用占位符的形式可读性、便捷性不佳,可考虑使用Lambda,延迟字符串的拼接,且使用更加便利。
  • 阿里巴巴Java开发手册-OOP 规约「23」可进行优化:循环拼接时须使用StringBuilder;在拼接大量的大容量字符串时,使用StringBuilder尽量指定初始容量。
  • 简单的字符串拼接可用任意方式,推荐直接使用“+”号拼接,可读性最优。
  • 尽量使用JDK等直接提供的特性「如“+”号拼接字符串,Synchronized关键词等」,因为编译器+JVM会持续对此进行优化,JDK升级即可获得更大的收益。除非有明确的理由可以自行实现类似的功能。
  • 在需要考虑线程安全的场景可以考虑使用StringBuffer进行字符串拼接,不过一般来说没有这种需求,故不应该使用StringBuffer,避免增加复杂性。

分析过程

环境

  • 系统: windows 10 21H1
  • JDK: OpenJDK 1.8.0_302

分析用示例代码:

@Slf4j
public class StringConcat {

    @SneakyThrows
    public static void main(String[] args) {
        log.info("java虚拟机预热开始");
        String[] strs = new String[6000000];
        for (int i = 0; i < strs.length; i++) {
            strs[i] = id();
        }
        loopStringJoiner(strs);
        loopStringJoin(strs);
        loopStringBuilder(strs);
        log.info("java虚拟机预热结束");
        Thread.sleep(1000);
        log.info("开始测试:");

        Thread.sleep(1000);
        Stopwatch stopwatchLoopPlus = Stopwatch.createStarted();
//        loopPlus(strs);
        log.info("loop-plus: " + stopwatchLoopPlus.elapsed(TimeUnit.MILLISECONDS));

        Thread.sleep(1000);
        Stopwatch stopwatchLoopStringBuilderCapacity = Stopwatch.createStarted();
        loopStringBuilderCapacity(strs);
        log.info("loop-stringBuilderCapacity: " + stopwatchLoopStringBuilderCapacity.elapsed(TimeUnit.MILLISECONDS));

        Thread.sleep(1000);
        Stopwatch stopwatchLoopStringBuilder = Stopwatch.createStarted();
        loopStringBuilder(strs);
        log.info("loop-stringBuilder: " + stopwatchLoopStringBuilder.elapsed(TimeUnit.MILLISECONDS));

        Thread.sleep(1000);
        Stopwatch stopwatchLoopJoin = Stopwatch.createStarted();
        loopStringJoin(strs);
        log.info("loop-String.join: " + stopwatchLoopJoin.elapsed(TimeUnit.MILLISECONDS));

        Thread.sleep(1000);
        Stopwatch stopwatchLoopStringJoiner = Stopwatch.createStarted();
        loopStringJoiner(strs);
        log.info("loop-stringJoiner: " + stopwatchLoopStringJoiner.elapsed(TimeUnit.MILLISECONDS));

        Thread.sleep(1000);
        Stopwatch stopwatchSimplePlus = Stopwatch.createStarted();
        for (int i = 0; i < 500000; i++) {
            simplePlus(id(), id(), id());
        }
        log.info("simple-Plus: " + stopwatchSimplePlus.elapsed(TimeUnit.MILLISECONDS));

        Thread.sleep(1000);
        Stopwatch stopwatchSimpleStringBuilder = Stopwatch.createStarted();
        for (int i = 0; i < 500000; i++) {
            simpleStringBuilder(id(), id(), id());
        }
        log.info("simple-StringBuilder: " + stopwatchSimpleStringBuilder.elapsed(TimeUnit.MILLISECONDS));

        Thread.sleep(1000);
        Stopwatch stopwatchSimpleStringBuffer = Stopwatch.createStarted();
        for (int i = 0; i < 500000; i++) {
            simpleStringBuffer(id(), id(), id());
        }
        log.info("simple-StringBuffer: " + stopwatchSimpleStringBuffer.elapsed(TimeUnit.MILLISECONDS));

    }

    private static String loopPlus(String[] strs) {
        String str = "";
        for (String s : strs) {
            str = str + "+" + s;
        }
        return str;
    }

    private static String loopStringBuilder(String[] strs) {
        StringBuilder str = new StringBuilder();
        for (String s : strs) {
            str.append("+");
            str.append(s);
        }
        return str.toString();
    }

    private static String loopStringBuilderCapacity(String[] strs) {
        StringBuilder str = new StringBuilder(strs[0].length() * strs.length);
        for (String s : strs) {
            str.append("+");
            str.append(s);
        }
        return str.toString();
    }

    private static String loopStringJoin(String[] strs) {
        StringJoiner joiner = new StringJoiner("+");
        for (String str : strs) {
            joiner.add(str);
        }
        return joiner.toString();
    }

    private static String loopStringJoiner(String[] strs) {
        return String.join("+", strs);
    }

    private static String simplePlus(String a, String b, String c) {
        return a + "+" + b + "+" + c;
    }

    private static String simpleStringBuilder(String a, String b, String c) {
        StringBuilder builder = new StringBuilder();
        builder.append(a);
        builder.append("+");
        builder.append(b);
        builder.append("+");
        builder.append(c);
        return builder.toString();
    }

    private static String simpleStringBuffer(String a, String b, String c) {
        StringBuffer buffer = new StringBuffer();
        buffer.append(a);
        buffer.append("+");
        buffer.append(b);
        buffer.append("+");
        buffer.append(c);
        return buffer.toString();
    }

    private static String id() {
        return UUID.randomUUID().toString();
    }

}

结果及总结

- java虚拟机预热开始
- java虚拟机预热结束
- 开始测试:
- loop-plus: 执行超时
- loop-stringBuilderCapacity: 285
- loop-stringBuilder: 1968
- loop-String.join: 1313
- loop-stringJoiner: 1238
- simple-Plus: 812
- simple-StringBuilder: 840
- simple-StringBuffer: 857

  • 多次测试,可发现在字符串循环拼接场景下,直接使用“+”号性能最低,有初始容量的StringBuilder性能最高,其他方式性能均没有太大差异。
  • 多次测试,可发现在字符串简单拼接场景下,使用“+”号、StringBuilder、StringBuffer性能差距在5%左右,可理解为测试误差,可认为三种方式性能一致。

代码及结果分析

1. StringBuilder与StringBuffer对比

在无争抢共享资源的场景下,JVM会使用偏向锁等方法优化,甚至会进行锁消除,使用Synchronized关键词与否,性能并无明显差异。

2. 字节码分析

对比上述#simplePlus和#simpleStringBuilder两个方法的字节码,可明显看到两方法执行内容基本一致,但是直接使用"+"号时处理流程更短,可见编译器进行了深度优化,使用优化后的字节码理论上会有更高的性能:

  // access flags 0xA
  private static simplePlus(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    // parameter  a
    // parameter  b
    // parameter  c
   L0
    LINENUMBER 125 L0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 0
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "+"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "+"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE a Ljava/lang/String; L0 L1 0
    LOCALVARIABLE b Ljava/lang/String; L0 L1 1
    LOCALVARIABLE c Ljava/lang/String; L0 L1 2
    MAXSTACK = 2
    MAXLOCALS = 3

  // access flags 0xA
  private static simpleStringBuilder(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    // parameter  a
    // parameter  b
    // parameter  c
   L0
    LINENUMBER 129 L0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ASTORE 3
   L1
    LINENUMBER 130 L1
    ALOAD 3
    ALOAD 0
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    POP
   L2
    LINENUMBER 131 L2
    ALOAD 3
    LDC "+"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    POP
   L3
    LINENUMBER 132 L3
    ALOAD 3
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    POP
   L4
    LINENUMBER 133 L4
    ALOAD 3
    LDC "+"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    POP
   L5
    LINENUMBER 134 L5
    ALOAD 3
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    POP
   L6
    LINENUMBER 135 L6
    ALOAD 3
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ARETURN
   L7
    LOCALVARIABLE a Ljava/lang/String; L0 L7 0
    LOCALVARIABLE b Ljava/lang/String; L0 L7 1
    LOCALVARIABLE c Ljava/lang/String; L0 L7 2
    LOCALVARIABLE builder Ljava/lang/StringBuilder; L1 L7 3
    MAXSTACK = 2
    MAXLOCALS = 4

到此这篇关于Java代码中4种字符串拼接方式分析的文章就介绍到这了,更多相关Java 字符串拼接内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java中拼接字符串的5种方法效率对比

    前言 最近写一个东东,可能会考虑到字符串拼接,想了几种方法,但对性能未知,所以下面就来测试下面,话不多说了,来一起看看详细的介绍吧. 示例代码 public class Test { List<String> list = new ArrayList<>(); @Before public void init(){ IntStream.range(0, 100000).forEach((index) -> { list.add("str" + index)

  • Java 字符串的拼接详解

    工作日忙于项目的逻辑实现,周六有点时间,从书柜里拿出厚厚的英文版Thinking In Java,读到了字符串对象的拼接.参考着这本书做个翻译,加上自己思考的东西,写上这篇文章记录一下. 不可变的String对象 在Java中,String对象是不可变的(Immutable).在代码中,可以创建多个某一个String对象的别名.但是这些别名都是的引用是相同的. 比如s1和s2都是"droidyue.com"对象的别名,别名保存着到真实对象的引用.所以s1 = s2 String s1

  • java 字符串的拼接的实现实例

    java 字符串的拼接的实现实例 在实际的开发工作中,对字符串的处理是最常见的编程任务.本题目即是要求程序对用户输入的串进行处理.具体规则如下: 1. 把每个单词的首字母变为大写. 2. 把数字与字母之间用下划线字符(_)分开,使得更清晰 3. 把单词中间有多个空格的调整为1个空格. 例如: 用户输入: you and     me what  cpp2005program 则程序输出: You And Me What Cpp_2005_program 用户输入: this is     a  

  • Java 字符串拼接竟然有这么多姿势(收藏版)

    但扪心自问,在我做程序员的前两年内,我也不知道为啥.遇到字符串拼接就上"+"号操作符,甭管是不是在循环体内.和小菜比起来,我当时可没他这么幸运,还有一位热心的"二哥"能够分享这份价值连城的开发手册. 既然我这么热心分享,不如好人做到底,对不对?我就认认真真地写一篇文章,给小菜解惑一下. 01."+"号操作符 要说姿势,"+"号操作符必须是字符串拼接最常用的一种了,没有之一. String chenmo = "沉默&q

  • Java中字符串拼接的一些细节分析

    工作日忙于项目的逻辑实现,周六有点时间,从书柜里拿出厚厚的英文版Thinking In Java,读到了字符串对象的拼接.参考着这本书做个翻译,加上自己思考的东西,写上这篇文章记录一下. 不可变的String对象 在Java中,String对象是不可变的(Immutable).在代码中,可以创建多个某一个String对象的别名.但是这些别名都是的引用是相同的. 比如s1和s2都是"droidyue.com"对象的别名,别名保存着到真实对象的引用.所以s1 = s2 复制代码 代码如下:

  • Java字符拼接成字符串的注意点详解

    这两天敲代码的时候,偶然间发现一个好玩的事情,分享一下,记录一下. 该段代码主要是:先产生的几个整数,把整数转换成对应的字符,最后的字符拼接成字符串,在把字符拼接成字符串的时候,个人因为偷懒使用+号进行操作,出现了一点小惊喜.拼接以后出现了两种不同的结果,感到十分的意外,所以分析了一下出现的结果,记录一下. package top.supertd.www; import java.util.concurrent.ThreadLocalRandom; public class TestString

  • Java代码中4种字符串拼接方式分析

    目录 结论 最佳实践 分析过程 环境 分析用示例代码: 代码及结果分析 本文研讨的字符串拼接方式为以下4种:“+”号.StringBuilder.StringJoiner.String#join,对比分析及探讨最佳实践. 结论 后面内容比较枯燥,所以先说结论: 本文研讨的字符串拼接方式为以下4种:“+”号.StringBuilder.StringJoiner.String#join 在简单的字符串拼接场景中「如:"a" + "b" + "c"」,

  • C++中两种字符串定义方式和区别介绍

    目录 前言 概念 定义方式 计算机的存储方式 总结 前言 在学习字符串这方面的内容时,发现字符串定义的两种方式虽然内容相同但还是有细微的区别 概念 字符串就是一串用双引号连接起来的字符串字面值,简称为字符串 定义方式 上代码 char acter1[]="Hello world";//第一种定义方式 char acter2[]={'H','e','l','l','o','w','o','r','l','d'}; 第二种定义方式 此时并不能看出这两种定义方式的区别,拿到VS调试器看看 很

  • 教你在JNA中将本地方法映射到JAVA代码中的示例

    目录 简介 Library Mapping Function Mapping Invocation Mapping 防止VM崩溃 性能考虑 总结 简介 不管是JNI还是JNA,最终调用的都是native的方法,但是对于JAVA程序来说,一定需要一个调用native方法的入口,也就是说我们需要在JAVA方法中定义需要调用的native方法. 对于JNI来说,我们可以使用native关键字来定义本地方法.那么在JNA中有那些在JAVA代码中定义本地方法的方式呢? Library Mapping 要想

  • go语言中五种字符串的拼接方式(小结)

    目录 +拼接方式 sprintf函数 Join函数 buffer.Builderbuffer.WriteString函数 buffer.Builder函数 ps:直接使用运算符 主要结论 +拼接方式 这种方式是我在写golang经常用的方式,go语言用+拼接,php使用.拼接,不过由于golang中的字符串是不可变的类型,因此用 + 连接会产生一个新的字符串对效率有影响. func main() { s1 := "hello" s2 := "word" s3 :=

  • 详解java代码中init method和destroy method的三种使用方式

    在java的实际开发过程中,我们可能常常需要使用到init method和destroy method,比如初始化一个对象(bean)后立即初始化(加载)一些数据,在销毁一个对象之前进行垃圾回收等等. 周末对这两个方法进行了一点学习和整理,倒也不是专门为了这两个方法,而是在巩固spring相关知识的时候提到了,然后感觉自己并不是很熟悉这个,便好好的了解一下. 根据特意的去了解后,发现实际上可以有三种方式来实现init method和destroy method. 要用这两个方法,自然先要知道这两

  • 浅谈JS中的三种字符串连接方式及其性能比较

    工作中经常会碰到要把2个或多个字符串连接成一个字符串的问题,在JS中处理这类问题一般有三种方法,这里将它们一一列出顺便也对它们的性能做个具体的比较. 第一种方法 用连接符"+"把要连接的字符串连起来: str="a"; str+="b"; 毫无疑问,这种方法是最便捷快速的,如果只连接100个以下的字符串建议用这种方法最方便. 第二种方法 以数组作为中介用 join 连接字符串: var arr=new Array(); arr.push(a);

  • IDEA插件之快速删除Java代码中的注释

    背景 有时,我们需要删除Java源代码中的注释.目前有不少方法,比如: 实现状态机.该方式较为通用,适用于多种语言(取决于状态机支持的注释符号). 正则匹配.该方式容易误判,尤其是容易误删字符串. 利用第三方库.该方式局限性较强,比如不同语言可能有不同的第三方库. 本文针对Java语言,介绍一种利用第三方库的方式,可以方便快速地移除代码中的注释. 原理 这个第三方库叫做JavaParser.它可以分析Java源码,并生成语法分析树(AST),其中注释也属于AST中的节点. 因此核心思路即为: J

  • Spring 3.x中三种Bean配置方式比较详解

    以前Java框架基本都采用了XML作为配置文件,但是现在Java框架又不约而同地支持基于Annotation的"零配置"来代替XML配置文件,Struts2.Hibernate.Spring都开始使用Annotation来代替XML配置文件了:而在Spring3.x提供了三种选择,分别是:基于XML的配置.基于注解的配置和基于Java类的配置. 下面分别介绍下这三种配置方式:首先定义一个用于举例的JavaBean. package com.chinalife.dao public cl

  • Python 中几种字符串格式化方法及其比较

    Python 中几种字符串格式化方法及其比较 起步 在 Python 中,提供了很多种字符串格式化的方式,分别是 %-formatting.str.format 和 f-string .本文将比较这几种格式化方法. %- 格式化 这种格式化方式来自于 C 语言风格的 sprintf 形式: name = "weapon" "Hello, %s." % name C 语言的给实话风格深入人心,通过 % 进行占位. 为什么 %-formatting不好 不好的地方在于,

  • Java代码中如何去掉烦人的“!=null”

    问题 为了避免空指针调用,我们经常会看到这样的语句 ...if (someobject != null) { someobject.doCalc();}... 最终,项目中会存在大量判空代码,多么丑陋繁冗!如何避免这种情况?我们是否滥用了判空呢? 精华回答: 这是初.中级程序猿经常会遇到的问题.他们总喜欢在方法中返回null,因此,在调用这些方法时,也不得不去判空.另外,也许受此习惯影响,他们总潜意识地认为,所有的返回都是不可信任的,为了保护自己程序,就加了大量的判空. 吐槽完毕,回到这个题目本

随机推荐