千家信息网

Java字符串的构造和拼接

发表于:2025-12-04 作者:千家信息网编辑
千家信息网最后更新 2025年12月04日,这篇文章主要讲解了"Java字符串的构造和拼接",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"Java字符串的构造和拼接"吧!2.1 构造字符串字符串在
千家信息网最后更新 2025年12月04日Java字符串的构造和拼接

这篇文章主要讲解了"Java字符串的构造和拼接",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"Java字符串的构造和拼接"吧!

2.1 构造字符串

字符串在Java里是不可变的,无论是构造,还是截取,得到的总是一个新字符串。看一下构造一个字符串源码

private final char value[];public String(String original) {  this.value = original.value;  this.hash = original.hash;}

原有的字符串的value数组直接通过引用赋值给新的字符串value,也就是俩个字符串共享一个char数组,因此这种构造方法有着最快的构造。Java里的String对象被设计为不可变。意思是指一旦程序获得了字符串对象引用,不必担心这个字符串在别的地方被修改,不可变意味着线程安全,在第三章对不可变对象线程安全性又说明。

构造字符串更多的情况构造字符串是通过一个字符串数组,或者在某些框架的反序列化,使用byte[] 来构造字符串,这种情况下性能会非常低。 如下是通过char[]数组构造一个新的字符串源码

public String(char value[]) {  this.value = Arrays.copyOf(value, value.length);}

Arrays.copyOf 会重新拷贝一份新的数组,方法如下

public static char[] copyOf(char[] original, int newLength) {  char[] copy = new char[newLength];  System.arraycopy(original, 0, copy, 0,                   Math.min(original.length, newLength));  return copy;}

可以看到通过数组构造字符串实际上是会创建一个新的字符串数组。如果不这样,还是直接引用char数组,那么外部如果更改char数组,则这个新的字符串就被改变了。

char[] cs = new char[]{'a','b'};String str = new String(cs);cs[0] ='!'

上面的代码最后一行,修改了cs数组,但不会影响str。因为str实际上是新的字符串数组构成

通过char数组构造新的字符串是最长用的方法,我们后面看到几乎每个字符串API,都会调用这个方法构造新的字符串,比如subString,concat等方法。如下代码验证了通过字符串构造新的字符串,以及使用char数组构造字符串性能比较

String str= "你好,String";char[] chars = str.toCharArray();[@Benchmark](https://my.oschina.net/u/3268003)public String string(){  return new String(str);}[@Benchmark](https://my.oschina.net/u/3268003)public String stringByCharArray(){  return new String(chars);}

输出按照ns/op来输出,既每次调用所用的纳秒数,可以看到通过char构造字符串还是先当耗时的,特别如果是数组特别长,那更加耗时

Benchmark                                  Mode     Score    Units     c.i.c.c.NewStringTest.string               avgt     4.235    ns/op     c.i.c.c.NewStringTest.stringByCharArray    avgt    11.704    ns/op

通过字节构造字符串,是一种非常常见的情况,尤其现在分布式和微服务流行,字符串在客户端序列化成字节数组,并发送给你给服务器端,服务器端会有一个反序列化,通过byte构造字符串

如下测试使用byte构造字符串性能测试

byte[] bs = "你好,String".getBytes("UTF-8");[@Benchmark](https://my.oschina.net/u/3268003)public String stringByByteArray() throws Exception{  return new String(bs,"UTF-8");}

测试结果可以看到byte构造字符串太耗时了,尤其是当要构造的字符串非常长的时候

Benchmark                                  Mode    Score    Units       c.i.c.c.NewStringTest.string               avgt    4.649    ns/op       c.i.c.c.NewStringTest.stringByByteArray    avgt   82.166    ns/op       c.i.c.c.NewStringTest.stringByCharArray    avgt   12.138    ns/op

通过字节数组构造字符串,主要涉及到转码过程,内部会调用 StringCoding.decode转码

this.value = StringCoding.decode(charsetName, bytes, offset, length);

charsetName表示字符集,bytes是字节数组,offset和length表示字节数组

实际负责转码的是Charset子类,比如sun.nio.cs.UTF_8的decode方法负责实现字节转码,如果在深入到这个类,你会发现,你看到的是冰上一角,冰上下面这是一个相当耗CPU计算转码的工作,属于无法优化的部分.

在我多次的系统性能优化过程中,都会发现通过字节数据组构造字符串总是排在消耗CPU比较靠前的位置,转码消耗的系统性能抵得上百行的业务代码。 因此我们系统在设计到分布式的,需要仔细设计需要传输的字段,尽量避免用String。比如时间可以用long类型来表示,业务状态也可以用int来表示。如下需要序列化的对象

public class OrderResponse{  //订单日期,格式'yyyy-MM-dd'  private String createDate;  //订单状态,"0"表示正常  private String status;}

可以改进成更好的定义,以减小序列化和反序列化负担。

public class OrderResponse{  //订单日期  private long  createDate;  //订单状态,0表示正常  private int status;}

关于在微服务中,序列化和反序列化传输对象,会在第四章和五章再次介绍对象的序列化

2.2 字符串拼接

JDK会自动将使用+号做的字符串拼接自动转化为StringBuilder,如下代码:

String a="hello";String b ="world "String str=a+b;

虚拟机会编译成如下代码

String str = new StringBuilder().append(a).append(b).toString();

如果你运行JMH测试这俩段代码,性能其实一样的,因为使用+连接字符串是一个常见操作,虚拟机对如上俩个代码片段都会做一些优化,虚拟使用-XX:+OptimizeStringConcat 打开字符串拼接优化,(默认情况下是打开的)。 如果采用以下代码,虽然看是跟上面的代码片段差不多,但虚拟机无法识别这种字符串拼接模式,性能会下降很多

StringBuilder sb = new StringBuilder();sb.append(a);sb.append(b);

运行StringConcatTest类,代码如下

String a = "select u.id,u.name from user  u";String b="  where u.id=? "   ;[@Benchmark](https://my.oschina.net/u/3268003)public String concat(){  String c = a+b;  return c ;}[@Benchmark](https://my.oschina.net/u/3268003)public String concatbyOptimizeBuilder(){  String c = new StringBuilder().append(a).append(b).toString();  return c;}@Benchmarkpublic String concatbyBuilder(){  //不会优化  StringBuilder sb = new StringBuilder();  sb.append(a);  sb.append(b);  return sb.toString();}

有如下结果说明了虚拟机优化起了作用

Benchmark                                           Mode    Score    Units         c.i.c.c.StringConcatTest.concat                     avgt   25.747    ns/op         c.i.c.c.StringConcatTest.concatbyBuilder            avgt   90.548    ns/op         c.i.c.c.StringConcatTest.concatbyOptimizeBuilder    avgt   21.904    ns/op

可以看到concatbyBuilder是最慢的,因为没有被JVM优化

这里说的JVM优化,指的是虚拟机JIT优化,我们会在第8章JIT优化说明

读者可以自己验证一下a+b+c这种字符串拼接性能,看一下是否被优化了

同StringBuilder类似的还有StringBuffer,主要功能都继承AbstractStringBuilder, 提供了线程安全方法,比如append方法,使用了synchronized关键字

@Overridepublic synchronized StringBuffer append(String str) {  //忽略其他代码  super.append(str);  return this;}

几乎所有场景字符串拼接都不涉及到线程同步,因此StringBuffer已经很少使用了,如上的字符串拼接例子使用StringBuffer,

  @Benchmark  public String concatbyBuffer(){    StringBuffer sb = new StringBuffer();    sb.append(a);    sb.append(b);    return sb.toString();  }

输出如下

Benchmark                                           Mode      Score   Unitsc.i.c.c.StringConcatTest.concatbyBuffer             avgt    111.417   ns/opc.i.c.c.StringConcatTest.concatbyBuilder            avgt     94.758   ns/op

可以看到,StringBuffer拼接性能跟StringBuilder相比性能并不差,这得益于虚拟机的"逃逸分析",也就是JIT在打开逃逸分析情况以及锁消除的情况下,有可能消除该对象上的使用synchronzied限定的锁。

逃逸分析 -XX:+DoEscapeAnalysis和 锁消除-XX:+EliminateLocks,详情参考本书第8章JIT优化

如下是一个锁消除的例子,对象obj只在方法内部使用,因此可以消除synchronized

void foo() {  //创建一个对象  Object obj = new Object();   synchronized (obj) {    doSomething();  }}

程序不应该依赖JIT的优化,尽管打开了逃逸分析和锁消除,但不能保证所有代码都会被优化,因为锁消除是在JIT的C2阶段优化的,作为程序员,应该在无关线程安全情况下,使用StringBuilder。

使用StringBuilder 拼接其他类型,尤其是数字类型,则性能会明显下降,这是因为数字类型转字符在JDK内部,需要做很多工作,一个简单的Int类型转为字符串,需要至少50行代码完成。我们在第一章已经看到过了,这里不再详细说明。当你用StringBuilder来拼接字符串,拼接数字的时候,你需要思考,是否需要一个这样的字符串。

2.10 BigDecimal

我们都知道浮点型变量在进行计算的时候会出现丢失精度的问题。如下一段代码

System.out.println(0.05 + 0.01);System.out.println(1.0 - 0.42);

输出: 0.060000000000000005 0.5800000000000001

可以看到在Java中进行浮点数运算的时候,会出现丢失精度的问题。那么我们如果在进行商品价格计算的时候,就会出现问题。很有可能造成我们手中有0.06元,却无法购买一个0.05元和一个0.01元的商品。因为如上所示,他们两个的总和为0.060000000000000005。这无疑是一个很严重的问题,尤其是当电商网站的并发量上去的时候,出现的问题将是巨大的。可能会导致无法下单,或者对账出现问题。

通常有俩个方法来解决这种问题,如果能用long来表示账户余额以分为单位,这是效率最高的。如果不能,则只能使用BigDecimal类来解决这类问题。

BigDecimal a = new BigDecimal("0.05");BigDecimal b = new BigDecimal("0.01");BigDecimal ret = a.add(b);System.out.println(ret.toString());

通过字符串来构造BigDecimal,才能保证精度不丢失,如果使用new BigDecimal(0.05),则因为0.05本身精度丢失,使得构造出来的BigDecimal也丢失精度。

BigDecimal能保证精度,但计算会有一定性能影响,如下是测试余额计算,用long表示分,用BigDecimal表示元的性能对比

BigDecimal a = new BigDecimal("0.05");BigDecimal b = new BigDecimal("0.01");long c = 5;long d = 1;@Benchmark@CompilerControl(CompilerControl.Mode.DONT_INLINE)public long addByLong() {  return (c + d);}@Benchmark@CompilerControl(CompilerControl.Mode.DONT_INLINE)public BigDecimal addByBigDecimal() {  return a.add(b);}

在我的机器行,上面代码都能进行精确计算,通过JMH,测试结果如下

Benchmark                                 Mode   Score    Units    c.i.c.c.BigDecimalTest.addByBigDecimal    avgt   8.373    ns/op    c.i.c.c.BigDecimalTest.addByLong          avgt   2.984    ns/op

感谢各位的阅读,以上就是"Java字符串的构造和拼接"的内容了,经过本文的学习后,相信大家对Java字符串的构造和拼接这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

字符 字符串 数组 代码 性能 方法 对象 序列 问题 情况 字节 时候 精度 测试 类型 线程 安全 订单 分析 可变 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 茂名服务器租用 为知笔记自建服务器如何收藏 怎么给云服务器添加软件 保密技术和网络安全 十大网络安全上市公司金桥 我的世界手机远古版本服务器 芜湖联想服务器阵列卡低成本 linux服务器查看软件版本 简易iis服务器 网络技术裁判员 软件开发分工合作常出现的问题 晋城做软件开发的大公司 马鞍山电大试卷数据库 和平精英全部服务器 百卓网络安全防护 密云区管理软件开发包括什么 西双版纳旅游团软件开发 网络安全与执法 物理 湖南管理软件开发有哪些 网络安全用法心得体会范文 网络安全教育黑板报材料 列举三个传统的数据库索引技术 浙江游戏app软件开发定制 南京快递软件开发 上海先进网络技术设置 密云区管理软件开发包括什么 现在国内对日软件开发怎么样 怎么从注册表删除数据库 日本那一年组建政府网络安全 常用的数据库系统软件有哪些
0