Java中八种字符串拼接方式,性能很意外

开发 前端
StringBuffer 提供了一个可变的字符序列。它允许对字符串进行动态操作而无需创建新的对象。值得一提的是,它被设计为线程安全的,这意味着它可以被多个线程安全地并发访问和修改。

环境:SpringBoot3.2.5

1. 简介

在开发中,字符串拼接是非常常见的操作,广泛应用于日志记录、数据处理、用户界面生成等场景。然而,不同的字符串拼接方式在性能上有着显著的差异,这一点往往被开发人员忽视。本文将详细介绍 Java 中的 8 种字符串拼接方式,并通过性能测试揭示这些方法的实际表现,结果令人意外。

Java中可以使用如下8种方式进行字符串的拼接:

  1. "+"操作符
  2. String#concat方法
  3. String#join方法
  4. String#format方法
  5. Stream流方式
  6. StringBuffer
  7. StringBuilder
  8. StringJoiner

下面我们依次介绍这8中方式;接下来的示例我们都将建立在JMH之上。

2. 实战案例

2.1 "+"操作符

这是最简单的方法,也是我们可能最熟悉的一种。它可以使用加号(+)运算符来连接字符串字面量、变量或者二者的组合:

@Benchmark
public void plusOperator(Blackhole hole) {
  String str1 = "Pack";
  String str2 = " xxxooo";
  String result = str1 + str2;
  hole.consume(result);
}

2.2 String#concat方法

concat() 方法由 String 类提供,可用于将两个字符串连接在一起。

@Benchmark
public void concat(Blackhole hole) {
  String str1 = "Pack";
  String str2 = " xxxooo";
  String result = str1.concat(str2);
  hole.consume(result);
}

2.3 String#join方法

String.join() 是 Java 8 以后新增的静态方法。它允许使用指定的分隔符连接多个字符串。

@Benchmark
public void join(Blackhole hole) {
  String str1 = "Pack"; 
  String str2 = " xxxooo"; 
  String result = String.join("", str1, str2);
  hole.consume(result);
}

2.4 String#format方法

String.format() 用于使用占位符和格式指定符格式化字符串。通过使用实际值替换占位符,可以创建格式化字符串。

@Benchmark
public void format(Blackhole hole) {
  String str1 = "Pack"; 
  String str2 = " xxxooo"; 
  String result = String.format("%s%s", str1, str2);
  hole.consume(result);
}

2.5 Stream流

它为在对象集合上执行操作提供了一种富有表现力的方法,并允许我们使用 Collectors.joining() 来集中字符串。

@Benchmark
public void stream(Blackhole hole) {
  List<String> strList = Arrays.asList("Pack", " xxxooo");
  String result = strList.stream().collect(Collectors.joining());
  hole.consume(result);
}

2.6 StringBuffer

StringBuffer 提供了一个可变的字符序列。它允许对字符串进行动态操作而无需创建新的对象。值得一提的是,它被设计为线程安全的,这意味着它可以被多个线程安全地并发访问和修改。

@Benchmark
public void stringBuffer(Blackhole hole) {
  StringBuffer buffer = new StringBuffer();
  buffer.append("Pack") ; 
  buffer.append(" xxxooo") ; 
  String result = buffer.toString() ;
  hole.consume(result) ;
}

2.7 StringBuilder

StringBuilder 和 StringBuffer 的用途相同。它们之间唯一的区别是 StringBuilder 不是线程安全的,而 StringBuffer 是。在不需要考虑线程安全的单线程场景中,StringBuilder 是非常完美的选择。

@Benchmark
public void stringBuilder(Blackhole hole) {
  StringBuilder builder = new StringBuilder() ; 
  builder.append("Pack") ; 
  builder.append(" xxxooo") ; 
  String result = builder.toString() ;
  hole.consume(result) ;
}

2.8 StringJoiner

StringJoiner 是从 Java 8 开始引入的一个新类。它的功能与 StringBuilder 类似,提供了一种使用分隔符连接多个字符串的方式。尽管它与 StringBuilder 有相似之处,但 StringJoiner 也不是线程安全的。

@Benchmark
public void stringJoiner(Blackhole hole) {
  StringJoiner joiner = new StringJoiner("");
  joiner.add("Pack") ;
  joiner.add(" xxxooo") ;
  String result = joiner.toString() ;
  hole.consume(result) ;
}

以上我们简单的介绍了每一种字符串拼接的使用。

3. 性能测试

接下来,我们通过JMH进行性能的测试,首先我们在类上添加如下的注解:

// 预热1s钟,预热3次
@Warmup(iterations = 3, time = 1)
// 启动多少个进程
@Fork(value = 1, jvmArgsAppend = {"-Xms512m", "-Xmx512m"})
// 指定显示结果(枚举值)
@BenchmarkMode(Mode.AverageTime)
// 指定显示结果单位(枚举值)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
// 迭代10次,每次2s
@Measurement(iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS)
public class StringJoinTest {
  // 上面8种字符串拼接的方法
}

在本示例中,我们将通过main的方式运行,这种方式稍微有点不是特别准确,你可以选择jar的方式。

public static void main(String[] args) throws Exception {
  Options options = new OptionsBuilder()
    // 你要测试的类
    .include(StringJoinTest.class.getSimpleName())
    // 启动几个进程
    .forks(1).build();
  new Runner(options).run();
}

最终测试结果如下:

图片图片

下面是对上面每一列的说明(以第一行concat测试为例说明)

Benchmark

a.说明:基准测试的名称,通常是类名+方法名。

b.示例:StringJoinTest.concat。

Mode

a.说明:基准测试的模式,常见模式有:

  • avgt:平均时间模式(Average Time),每个操作的平均时间。
  • thrpt:吞吐量模式(Throughput),单位时间内完成的操作次数。
  • sample:采样模式,用于收集详细的统计信息。
  • ss:稳定状态模式,用于评估长时间运行的性能稳定性。

  b.示例:avgt

Cnt

  • 说明:迭代次数,即基准测试运行的次数。
  • 示例:10

Score

a.说明:基准测试的主要结果指标。根据模式的不同,这个值的含义也不同。

  • avgt:每个操作的平均时间(秒、毫秒、纳秒等)。
  • thrpt:每秒完成的操作次数(ops/s)。

b.示例:8.584

Error

  • 说明:结果的标准误差(Standard Error),表示结果的不确定性。
  • 示例:0.175

Units

  • 说明:结果的单位
  • 示例:ns/op(每操作纳秒)

性能排序

  1. (+)plusOperator:6.154 ± 0.119 ns/op
  2. concat:8.584 ± 0.175 ns/op
  3. stringBuilder:11.560 ± 0.216 ns/op
  4. stringBuffer:12.340 ± 0.150 ns/op
  5. stringJoiner:29.932 ± 0.236 ns/op
  6. join:28.210 ± 0.241 ns/op
  7. stream:34.293 ± 0.284 ns/op
  8. format:409.691 ± 2.941 ns/op
责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2013-06-24 15:16:29

Java字符串拼接

2024-12-20 12:10:19

2023-10-31 18:57:02

Java字符串

2024-12-23 07:38:20

2023-12-11 08:39:14

Go语言字符串拼

2019-02-27 09:08:20

Java 8StringJoineIDEA

2011-07-11 16:00:22

字符串拼接

2022-11-25 07:53:26

bash脚本字符串

2023-06-12 08:17:38

Java字符串拼接

2021-06-11 18:08:00

Java字符串拼接

2019-12-17 15:49:44

Java语言字符串

2022-03-18 09:42:54

JavaString

2016-12-27 09:46:55

Java 8StringBuild

2017-01-19 11:26:55

Java 8StringBuild

2021-05-31 07:57:00

拼接字符串Java

2016-10-12 10:18:53

Java字符串源码分析

2019-12-25 15:41:50

JavaScript程序员编程语言

2021-10-31 23:01:50

语言拼接字符串

2024-10-30 16:49:00

Python字符串

2023-09-07 11:29:36

API开发
点赞
收藏

51CTO技术栈公众号