百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

【Java深度干货】如何高效构造字符串(String)?

liebian365 2025-03-14 18:35 3 浏览 0 评论

字符串在 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 笔试和面试的参考书。

相关推荐

看黑客是如何获取你电脑最高权限的,一定要看

在渗透过程中,通过各种方式获取到一枚cmdshell,但是这个shell的权限比较低,无法让我们做我们想要做的一些操作,比如说获取系统密码,获取数据库信息,又或者比如说拿到服务器中的另一个站点的权限,...

是50个常用的Visual Basic代码示例:

以下是50个常用的VisualBasic代码示例:1.声明变量```vb...

电脑系统型号怎么看版本(如何看电脑系统型号)

有时候我们会需要进行查看电脑上安装的windows系统版本及系统版本号,但对于不懂电脑知识的小白来说要怎么查看电脑系统版本信息呢?别着急,有小编在接下来,就将查看电脑系统版本的教程来分享给你们,希望对...

dos命令systeminfo,查看系统启动时间。电脑卡慢,win10怎么了?

最近一段时间,有几个反应电脑卡慢的,都是windows10的系统。询问得知每天电脑有关机,打开任务管理器,内存使用量达到百分之九十多,而程序只打开微信、wps、360浏览器。cmd窗口运行命令syst...

systeminfo命令:全面解析系统信息!

你是否曾想过,仅凭一条简单的命令,就能深入了解计算机的"内心世界"?是不是有点不可思议?那么,让我们一起探寻这个神奇的命令,揭开它背后的奥秘吧!它能提供的信息超乎你的想象,从操作系统到硬件配置,再到驱...

电脑序列号怎么查询?只需两行命令一键查询

当我们的电脑出问题需要保修的时候,需要查询到电脑的型号和序列号才更便于进行下一步的操作,有包装盒的朋友还可以在包装盒上查询,笔记本用户可以在电脑底部标签上查询,没有包装盒和标签破损的用户就无从下手了。...

快速显示系统信息:Systeminfo命令详解

Systeminfo命令是windows系统中显示系统信息的命令,此命令可以显示出计算机的操作系统的详细配置信息,包括操作系统配置、安全信息、产品ID和硬件属性(如RAM、磁盘空间和网卡)。使用...

dos命令systeminfo图文教程,显示操作系统配置信息msinfo32

大家好,我是老盖,首先感谢观看本文,本篇文章做的有视频,视频讲述的比较详细,也可以看我发布的视频。今天我们学习systeminfo命令,该工具显示本地或远程机器(包括服务包级别)的操作系统配置的信息,...

基于uniapp+vue3跨端仿制chatgpt实例uniapp-chatgpt

#夏日生活打卡季#...

原创新作uniapp+vue3+pinia2高仿微信App聊天

前段时间有给大家分享一个flutter3.x桌面端os系统。今天再分享一款最新原创之作uniapp-vue3-wechat聊天实例。uni-vue3-wechat采用...

UniApp开发的设备适配(uniapp服务器配置)

UniApp是一个跨平台开发框架,支持多端应用(如H5、小程序、iOS、Android等)。由于不同设备的屏幕尺寸、分辨率、操作系统等存在差异,设备适配是开发过程中需要重点关注的问题。以下是Uni...

如何用服务器搭建自己的个人网站(自己服务器怎么做网站)

这篇教程主要是告诉大家如何利用TCP和HTTP协议来完成网站的搭建。首先你需要有C/C++语言基础,且有服务器、客户端概念,如果你了解TCP或者HTTP协议的话,那么将会帮助你更快的学会如何搭建个人网...

大话C语言:字符数组(c语言字符数组教学视频)

1字符数组概述C语言中没有字符串这种数据类型,可以通过char的数组来替代。数字0(和字符'\0'等价)结尾的char数组就是一个字符串,字符串是一种特殊的char的数组。...

源码分享:在pdf上加盖电子签章(pdf怎么加电子签章)

在pdf上加盖电子签章,并不是只是加个印章图片,。而是要使用一对密钥中的私钥对文件进行签字。为啥要用私钥呢?很简单,因为公钥是公开的,其他人才可以用公钥为你证明,这个文件是你签的。这就是我们常说的:私...

C语言wcstombs函数详解:宽字符字符串到多字节的「翻译官」

核心定位wcstombs是C语言中用于将宽字符字符串转换为多字节字符串的「翻译官」,它能将宽字符(wchar_t)转换为多字节字符(如UTF-8编码的中文)。就像一位翻译官,它能将一种语言(宽字符...

取消回复欢迎 发表评论: