【Java深度干货】如何高效构造字符串(String)?
wptr33 2025-07-28 00:36 21 浏览
字符串在 Java 中是不可变的,无论构造,还是截取,得到的总是一个新字符串。下面看一下构造一个字符串(String)的源码:
private final char value[];
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
原有的字符串的 value 数组直接通过引用赋值给新的字符串的 value 数组,也就是两个字符串共享一个 char[],因此这种构造方法有着最快的构造速度。
Java 中的 String 对象被设计为不可变,是指一旦程序获得字符串对象引用,则不必担心这个字符串在别的地方被修改,因为修改总意味着获得一个新的字符串,不可变意味着线程安全,不必担心并发修改。
更多时候是通过一个 char[],或者在某些分布式框架反序列化对象时使用 byte[]来构造字符串的,这种情况下性能会非常低。
以下是通过 char[]构造一个新的字符串的源码:
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
Arrays.copyOf 会重新复制一份新的数组,方法如下:
//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 数组,则这个新的字符串就被改变了。
char[] cs = new char[]{'a','b'};
String str = new String(cs);
cs[0] ='!'
上面的代码最后一行修改了 cs 数组,但不会影响 str。因为 str 实际上是由新的字符串数组构成的。
通过 char[]构造新的字符串是最常用的方法,后面会看到几乎每个修改的 API,都会调用这个方法构造新的字符串,比如 subString、concat、replace 等。
以下代码验证了通过字符串构造新的字符串,以及使用 char[]构造字符串的性能比较:
String str= "你好,String";
char[] chars = str.toCharArray();
@Benchmark
public String string(){34 │
return new String(str);
}
@Benchmark
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 构造字符串。
使用 byte[]构造字符串的性能测试:
byte[] bs = "你好,String".getBytes("UTF-8");
@Benchmark
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 计算资源的工作,属于无法优化的部分。
Unicode 是字符集,为每一个字符分配一个编号,UTF-8 是一种将字符转为二进制编码的规则。
UTF-8 一种变长字节编码方式,对于某一个字符的 UTF-8 编码,如果只有一个字节,则其最高二进制位为 0;如果是多字节,则其第一个字节从最高位开始,连续的二进制位值为 1 的个数决定了其编码的位数,其余各字节均以 10 开头。
UTF-8 最多可用到 6 个字节,Unicode 在转为 UTF-8 时需要用到下面的模板:
比如“汉”字的 Unicode 码是 6C49,二进制编码是 0110_1100_0100_1001。如果编码成 UTF-8,则需要用到三字节模板,即表格中的最后一行:1110xxxx 10xxxxxx 10xxxxxx。
将 6C49 按照三字节模板的分段方法分为 0110_110001_001001,依次代替模板中的 x,得到 1110-0110 10-11000110-001001,即 E6 B1 89,这就是其 UTF-8 的编码。
在多次的系统性能优化过程中,会发现通过字节数组构造字符串总是排在消耗 CPU 比较靠前的位置,转码消耗的系统性能相当于上百行的业务代码。
因此在设计分布式系统时,需要仔细设计传输的字段,尽量避免用 String,比如时间可以用 long 类型来表示,业务状态可以用 int类型来表示。需要序列化的对象如下:
public class OrderResponse{
//订单日期,格式为 yyyy-MM-dd
private String createDate;
//订单状态,0 表示正常
private String status;
private String payStatus;
}
可以改进成更好的定义,以减小序列化和反序列化的负担:
public class OrderResponse{
//订单日期
private long createDate;
//订单状态,0 表示正常
private int status;
private int payStatus;
}
有的系统为了高效地存储和传输 OrderResponse 对象,甚至会把 status 和 payStatus 合成一个 int 类型的值,高位表示 status,低位表示 payStatus。
内容摘自《高性能Java系统权威指南》第二章
本书特点:
内容上,总结作者从事Java开发20年来在头部IT企业的高并发系统经历的真实案例,极具参考意义和可读性。
对于程序员和架构师而言,Java 系统的性能优化是一个超常规的挑战。这是因为 Java 语言和 Java 运行平台,以及 Java 生态的复杂性决定了 Java 系统的性能优化不再是简单的升级配置或者简单的 “空间换时间”的技术实现,这涉及 Java 的各种知识点。
本书从高性能、易维护、代码增强以及在微服务系统中编写Java代码的角度来描述如何实现高性能Java系统,结合真实案例,让读者能够快速上手实战。
风格上,本书的风格偏实战,读者可以下载书中的示例代码并运行测试。读者可以从任意一章开始阅读,掌握性能优化知识为公司的系统所用。
本书适合:
中高级程序员和架构师;
以及有志从事基础技术研发、开源工具研发的极客阅读;
也可以作为 Java 笔试和面试的参考书。
相关推荐
- 继续学习Python中的while true/break语句
-
上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个...
- python continue和break的区别_python中break语句和continue语句的区别
-
python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...
- 简单学Python——关键字6——break和continue
-
Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
-
用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...
- Python 中 break 和 continue 傻傻分不清
-
大家好啊,我是大田。...
- python中的流程控制语句:continue、break 和 return使用方法
-
Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...
- L017:continue和break - 教程文案
-
continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...
- 作为前端开发者,你都经历过怎样的面试?
-
已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...
- 面试被问 const 是否不可变?这样回答才显功底
-
作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...
- 2023金九银十必看前端面试题!2w字精品!
-
导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。...
- 前端面试总结_前端面试题整理
-
记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...
- 由浅入深,66条JavaScript面试知识点(七)
-
作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录...
- 2024前端面试真题之—VUE篇_前端面试题vue2020及答案
-
添加图片注释,不超过140字(可选)...
- 今年最常见的前端面试题,你会做几道?
-
在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...
- 一套比较完整的字节面试题,包含计算机网络、操作系统、前端等
-
一、算法和数据结构实现一个函数,判断两个给定的字符串是否为异构字符串。...
- 一周热门
- 最近发表
-
- 继续学习Python中的while true/break语句
- python continue和break的区别_python中break语句和continue语句的区别
- 简单学Python——关键字6——break和continue
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
- Python 中 break 和 continue 傻傻分不清
- python中的流程控制语句:continue、break 和 return使用方法
- L017:continue和break - 教程文案
- 作为前端开发者,你都经历过怎样的面试?
- 面试被问 const 是否不可变?这样回答才显功底
- 2023金九银十必看前端面试题!2w字精品!
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)