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

网友投稿 235 2022-08-28


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 21H1JDK: 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 + "VyYuirZeR+" + 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();

}

http:// 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: http://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. ()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. ()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

VyYuirZeRALOAD 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


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:python中的collections模块
下一篇:python之内置operator模块(python中内置的操作符函数)(Python中operator)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~