Java中连接字符串的最佳方法

最近有人问我这个问题–在Java中使用+运算符连接字符串是否对性能不利?

这让我开始思考Java中连接字符串的不同方法,以及它们如何相互对抗。 这些是我要研究的方法:

  1. 使用+运算符
  2. 使用StringBuilder
  3. 使用StringBuffer
  4. 使用String.concat()
  5. 使用String.joinString.join新增功能)

我也尝试了String.format()但是那太慢了,以至于我暂时不在本文中介绍。

在继续之前,我们应该分离两个用例:

  1. 将两个字符串作为单个调用连接在一起,例如在日志消息中。 因为这只是一个电话,您可能会认为性能几乎不是问题,但结果仍然很有趣,并且可以阐明该主题。
  2. 在一个循环中连接两个字符串。 性能尤其是一个问题,尤其是在循环较大的情况下。

我最初的想法和问题如下:

  1. +运算符是用StringBuilder实现的,因此至少在连接两个String的情况下,它应产生与StringBuilder类似的结果。 幕后到底发生了什么?
  2. 在所有类都是出于连接字符串并取代StringBuffer的目的而设计的之后,StringBuilder应该是最有效的方法。 但是,与String.concat()相比,创建StringBuilder的开销是多少?
  3. StringBuffer是用于连接字符串的原始类–不幸的是,其方法是同步的。 确实不需要同步,并且随后将其替换为未同步的StringBuilder。 问题是,JIT是否优化了同步?
  4. String.concat()应该适用于2个字符串,但是在循环中是否可以正常工作?
  5. String.join()具有比StringBuilder更多的功能,如果我们指示它使用空的定界符来联接String,它将如何影响性能?

我要解决的第一个问题是+运算符的工作方式。 我一直都知道它在幕后使用了StringBuilder,但是要证明这一点,我们需要检查字节码。

如今 ,查看字节码最简单的方法是使用JITWatch ,这是一个非常出色的工具,旨在了解JIT如何编译您的代码。 它有一个很棒的视图,您可以在其中与字节码(如果要进入该级别,还可以是机器码)并排查看源代码。

屏幕截图2015-02-17 at 17.27.46

这是一个非常简单的方法plus2()的字节码,我们可以看到确实在第6行上创建了一个StringBuilder并附加了变量a(第14行)和b(第18行)。

我认为将其与StringBuffer的手工使用进行比较会很有趣,因此我创建了另一个方法build2(),结果如下。

屏幕截图2015年2月17日的17.31.37

此处生成的字节码不如plus()方法那么紧凑。 StringBuilder存储在变量高速缓存中(第13行),而不是仅留在堆栈上。 我不知道为什么会这样,但是JIT可能能够做到这一点,我们将不得不看看时机如何。

无论如何,如果用plus运算符和StringBuilder将2个字符串连接起来的结果显着不同,那将是非常令人惊讶的。

我写了一个小型的JMH测试来确定不同方法的执行方式。 让我们首先看一下两个Strings测试。 参见下面的代码:

package org.sample;import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;import java.util.UUID;
import java.util.concurrent.TimeUnit;@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Thread)
public class LoopStringsBenchmark {private String[] strings;@Setuppublic void setupTest(){strings = new String[100];for(int i = 0; i<100; i++) {strings[i] = UUID.randomUUID().toString().substring(0, 10);}}@Benchmarkpublic void testPlus(Blackhole bh) {String combined = "";for(String s : strings) {combined = combined + s;}bh.consume(combined);}@Benchmarkpublic void testStringBuilder(Blackhole bh) {StringBuilder sb = new StringBuilder();for(String s : strings) {sb.append(s);}bh.consume(sb.toString());}@Benchmarkpublic void testStringBuffer(Blackhole bh) {StringBuffer sb = new StringBuffer();for(String s : strings) {sb.append(s);}bh.consume(sb.toString());}@Benchmarkpublic void testStringJoiner(Blackhole bh) {bh.consume(String.join("", strings));}@Benchmarkpublic void testStringConcat(Blackhole bh) {String combined = "";for(String s : strings) {combined.concat(s);}bh.consume(combined);}
}

结果看起来像这样:

屏幕+拍摄+ 2015-02-17 + at + 17.41.26

显而易见的赢家是String.concat()。 毫不奇怪,因为它不必为每次调用创建StringBuilder / StringBuffer而付出性能损失。 虽然确实需要每次都创建一个新的String(这将在以后变得很重要),但是对于连接两个Sting的非常简单的情况,它更快。

另一点是,尽管产生了额外的字节码,但正如我们预期的那样,plus和StringBuilder是等效的。 StringBuffer仅比StringBuilder慢一点,这很有趣,这表明JIT必须在做一些魔术来优化同步。

下一个测试将创建一个由100个字符串组成的数组,每个字符串包含10个字符。 基准比较了将100个字符串连接在一起的不同方法所花费的时间。 参见下面的代码:

这次的结果看起来完全不同:

屏幕截图2015-02-17 at 17.54.37

在这里,加号方法确实遭受了损失。 每次循环时创建StringBuilder的开销都非常大。 您可以在字节码中清楚地看到这一点:

屏幕截图2015-02-17 at 17.59.46

您可以看到每次执行循环时都会创建一个新的StringBuilder(第30行)。 可以争论的是,JIT应该发现这一点并能够对其进行优化,但是事实并非如此,并且使用+变得非常慢。

同样,StringBuilder和StringBuffer的性能完全相同,但是这次它们都比String.concat()快。 String.concat()为在循环的每次迭代中创建新的String付出的代价最终会增加,并且StringBuilder变得更有效率。

给定可以添加到此方法的所有其他功能,String.join()效果很好,但是,正如预期的那样,对于纯串联而言,它不是最佳选择。

摘要

如果要在单行代码中连接字符串,我将使用+运算符,因为它最易读,并且性能对于单次调用实际上并不重要。 还要提防String.concat(),因为您几乎肯定会需要执行空检查 ,而其他方法则不需要这样做。

在循环中串联字符串时,应使用StringBuilder。 您可以使用StringBuffer,但我不一定在所有情况下都信任JIT来像基准测试中那样高效地优化同步。

我的所有结果都是使用JMH取得的,并且都带有通常的健康警告 。

翻译自: https://www.javacodegeeks.com/2015/02/optimum-method-concatenate-strings-java.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/360884.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

时间转换以及公式

moment(item.updatedAt).fromNow() ;//距离今天多久了moment&#xff08;&#xff09;获得今天、明天和昨天的日期 let today moment(new Date());let tomorrow moment(new Date()).add(1,days);let yesterday moment(new Date()).add(-1, days);后续遇到继续补充

只不过是R.java文件的特性-----出错信息:R.java was modified manually! Reverting to generated version!...

出错信息:R.java was modified manually! Reverting to generated version! 出错原因&#xff1a;今天在res下建立了一个drawable的文件夹存放图片资源----图片名为1.jpg 后来R.java文件就报错了&#xff1a;R.java was modified manually! Reverting to generated version! 看…

十大最常见的Java性能问题

Java性能是所有Java应用程序开发人员都关心的问题&#xff0c;因为快速使应用程序与使其正常运行同等重要。 史蒂文海恩斯&#xff08;Steven Haines&#xff09;使用他在Java性能问题上的个人经验得出的结论是&#xff0c; 大多数问题都有共同的根本原因 。 因此&#xff0c;作…

antd form 初始化时间

initialValue: moment(record.showTime, YYYY-MM-DD HH:mm:ss)

uni-app 组件中的canvas转化为图片报错:errMsg:“canvasToTempFilePath:fail canvas is empty”

项目场景&#xff1a; uni-app,开发微信小程序 使用&#xff1a; wx.canvasToTempFilePath({canvasId: line,success: function(res) {console.log(canvasToImg, res);this.radarImg res.tempFilePath;},fail: function(res) {console.log(res);}});问题描述&#xff1a; ec…

正确获取Java事件通知

实现观察者模式以提供Java事件通知似乎是一件容易的事。 但是&#xff0c;容易陷入一些陷阱。 这是我在各种场合不慎造成的常见错误的解释…… Java事件通知 让我们从一个简单的bean StateHolder开始&#xff0c;它封装了带有适当访问器的私有int字段state &#xff1a; publ…

使用fn函数控制页面显示内容

在使用&#xff25;&#xff2c;的时候&#xff0c;不可避免的遇到&#xff0c;截取字符串&#xff0c;判断字符串长度等情况。这里给出简单的通过&#xff46;&#xff4e;函数操作字符串的deamon。 1、页面引入标签 <% taglib prefix"c" uri"http://java.s…

uni-app微信获取手机号,第一次解密总是失败

项目场景&#xff1a; uni-app; 获取code&#xff0c;后台解密手机号 问题描述&#xff1a; 每次第一次登陆&#xff0c;后台都会解密失败 原因分析&#xff1a; code获取错误&#xff1b;导致后台的解密key与code不对应 解决方案&#xff1a; 小程序获取手机号之前&#xf…

Java中不一致的操作会扩大规则

总览 当您在Java中执行一元或二进制操作时&#xff0c;标准行为是使用最宽的操作数&#xff08;或对byte &#xff0c; short和char使用更宽的操作数&#xff09;。 这很容易理解&#xff0c;但是如果考虑最佳类型可能会造成混淆。 乘法 当执行乘法运算时&#xff0c;您得到的…

圣诞节到了,用js给喜欢的人写一颗圣诞树吧

文章目录 1、效果预览2、代码2.1、定义数组写下祝福语2.2、模拟雪花落下的效果2.3、设置背景粒子2.4、操作动画效果2.5、定义闪烁效果2.6、定义粒子对象2.7、粒子对象播放2.8、绘制星星2.9、绘制圣诞树2.10、绘制星星背景动画2.11、定义初始化函数并调用 3、结尾 1、效果预览 圣…

Unity3D 访问Access数据库

Unity3D 访问Access数据库 在开始这个小教程之前呢&#xff0c;其实在网上你已经可以找到相关的资料了&#xff0c;但是我还是要把我自己做练习的一点东西分享出来。写这个教程的主要原因呢&#xff0c;是一个朋友在u3d的官网论坛里&#xff0c;找到了这个demo&#xff0c;但是…

uni.reLaunch前出现uni.showToast,不会成功弹出提示信息

解决方案&#xff1a; uni.showToast({title: 发布成功,duration: 1000});setTimeout(function() {uni.reLaunch({url: /pages/tips/index})}, 1000);

LaTeX 基础笔记。开篇

LaTeX 的起源非常牛逼&#xff0c;有一套书大家可能听说过《计算机程序设计艺术》&#xff0c;写了好几本。当然能在计算机方面写上艺术俩字的书恐怕不是我们一般人能读懂得东西了。他的作者在1976年准备写第二卷的时候发现计算机的排版非常难看&#xff0c;所以&#xff0c;为…

Java旧版不断发展

我最近偶然发现了JDK API的一个非常有趣的警告&#xff0c;即Class.getConstructors()方法。 它的方法签名是这样的&#xff1a; Constructor<?>[] getConstructors()有趣的是&#xff0c; Class.getConstructor(Class...)返回一个Constructor<T> &#xff0c;并…

React 学习笔记 —— Ref Hook

用以下三种方式创建 Ref 都可以 import React from reactexport default function Count () {const [count ,setCount] React.useState(0)const myRef React.createRef()const myRef2 React.useRef() // Ref Hook 的方式const myRef3 {current: undefined}const addNumber…

MFC消息机制

MFC消息机制 MFC消息机制涉及许多知识&#xff0c;比如消息分类&#xff0c;消息映射等。知识先了解一下&#xff0c;马上动手实践才是硬道理。我建了个SDI项目&#xff0c;把常用的消息试验了一遍。如果像我一样初学的&#xff0c;可以留下邮箱索取源码。// MainFrm.h afx_msg…

带Lambda表达式的Apache Wicket

这是怎么回事&#xff1f; :) 我一直在从事一些项目&#xff0c;这些项目值得庆幸的是将Apache Wicket用于表示层。 我自然想到Java的8个lambda表达式如何与Wicket完美匹配。 而不仅仅是我&#xff0c; Wicket团队似乎已经在努力更改API&#xff0c;以为开箱即用的lambda提供支…

React 父组件和子组件中的方法相互调用

目录父组件调用子组件方法子组件调用父组件方法父组件调用子组件方法 父组件中调用子组件的getTree方法 父组件 setFormValue()>{this.TreeList.getTree}<TreeList onSelect{this.setFormValue} onRef{(ref) > { this.TreeList ref }} />子组件 componentDidMount…

元素在父元素内垂直居中的思路

1.使用表格 的垂直居中特性 2.div的绝对定位 已知高度的情况下比较好弄. 3.用背景实现.前景元素visibility:hidden; 4.父元素table-cell 5.line-height 图片会跟随文字垂直居中.转载于:https://www.cnblogs.com/fumj/archive/2013/03/27/2984623.html

装饰者模式如何拯救了我的一天

在工作中&#xff0c;我正在处理庞大的Java代码库&#xff0c;该代码库是由许多不同的开发人员在15年的时间里开发的。 并不是所有的事情都由书来完成&#xff0c;但是同时我通常没有机会重构遇到的每一个奇怪之处。 尽管如此&#xff0c;仍可以每天采取提高代码质量的措施。 …