JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理

这篇具有很好参考价值的文章主要介绍了JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:JAVASE进阶:String常量池内存原理分析、字符串输入源码分析
📚订阅专栏:JAVASE进阶
希望文章对你们有所帮助

这是比较重要的内容,学习原理很重要,啃源码也很重要!!!

字符串常量的"+"拼接效率很高,但涉及大量字符串变量的拼接的时候效率就会大打折扣,除了时间耗费大,内存也会耗费很大,所以在这种时候我们会经常使用StringBuilder函数或者StringJoiner函数来进行字符串变量拼接。

因此在这里剖析一下字符串常规拼接方式的底层原理,并了解为何StringBuilder能大大提高字符串拼接的效率。最后啃一啃StringBuilder的源码,摸清StringBuilder的底层运行原理。

字符串拼接

字符串的拼接,实际上是有两种情况的,一种是等式的右边不包含变量的,一种是等式的右边有变量的:

1、等式右边无变量:

String s = "a" + "b" + "c";
System.out.println(s);

2、等式右边有变量:

String s1 = "a";
String s2 = s1 + "b";
String s3 = s2 + "c";
System.out.println(s3);

这两种方式的底层都是有对应的原理或机制的。

等式右边无变量

拼接的时候没有变量,都是字符串,就会触发字符串的优化机制。
也就是说,在编译的时候已经是最终的结果了。
也就是说,原先的.java文件,在编译成.class文件的时候,语句可以视为变成了:

String s = "abc";
System.out.println(s);

这种方式非常简单。

等式右边有变量(JDK8以前的源码分析)

拼接的时候有变量参与,这时候就会很复杂了。

JDK8以前,底层会使用StringBuilder进行拼接,在这里讲解一下JDK8以前,代码运行的步骤:

1、String s1 = "a",会在字符串常量池中创建一个字符串"a",s1记录a的地址值
2、String s2 = s1 + "b"
(1)在字符串常量池中创建一个字符串"b"
(2)在堆内存中创建一个StringBuilder(),通过append方法将"a"和"b"都放到StringBuilder容器中
(3)StringBuilder()执行toString()将容器转换为String类型的"ab"
也就是说,底层进行的代码是:
new StringBuilder().append(s1).append("b").toString()
3、String s3 = s2 + "c"与第2步同理。

反正最终都是要变成一个字符串并返回地址给变量,那么StringBuilder是怎么变成字符串的,toString到底是如何执行的,需要阅读源码:

1、Ctrl+N点开搜索按钮,查找java.lang包下的StringBuilder:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
2、进入后点击Ctrl+(Fn)+F12查找toString()方法:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
3、Ctrl+B跟踪进入:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
看到new关键字就可以确定了,等式右边有变量,除了要new出StringBuilder对象,还会因为toString方法去new出一个字符串。

因此,JDK8以前的这种方法,一个加号,堆内存就要new出两个对象,非常浪费性能。

JDK8字符串拼接的底层原理

执行下列语句:

String s1 = "a";
String s2 = "b";
String s3 = "c";
String s4 = s1 + s2 + s3;
System.out.println(s4);

JDK7执行这一段代码,至少要new出4个对象,显然是很差劲的方式。

JDK8默认的方式:预估字符串的长度,并创建一个数组

“a” “b” “c”

这种方式看起来会很便捷,但是实际上,预估也是需要时间的,如果很多行都有"+"操作,就要做很多次的预估,依旧会影响效率,创建的数组数量也会更多,时间与空间并没有实质性的优化。

因此,如果都是字符串常量拼接,可以直接"+";如果很多字符串变量拼接,不要直接"+",会在底层创建多个对象,浪费时间和性能

其实StringBuilder本身是很高效的方式的,但是字符串变量的多次"+"导致创建多次StringBuilder显然也是不合理的。因此当有很多字符串变量的时候,最好的方式还是使用StringBuilder或者StringJoiner来append或add进去,最后再利用toString方法转换为字符串,整个过程只会new出2个对象。

简单常见面试题

1、等式右边无变量的情况:

String s1 = "abc";
String s2 = "a" + "b" + "c";
System.out.println(s1 == s2);

显然,既然编译的时候已经将s2的右边看做是"abc"了,那么就会复用常量池中的"abc",因此答案为true

2、等式右边有变量的情况:

String s1 = "abc";
String s2 = "ab";
String s3 = s2 + "c";
System.out.println(s1 == s3);

s1会在常量池中生成,但是s3的创建,JDK8以前会先在底层创建StringBuilder对象再toString转化为字符串,JDK8以后先预估数组对象再转化为字符串,无论哪种方式,最后都是在堆空间中new出一个字符串对象的,而又因为引用变量比较的是地址值,所以答案为false

这都是很简单的面试题,注意分析一下等式右边从哪里来的就行了。

StringBuilder源码分析

那么StringBuilder为何高效?同样需要分析源码。在分析源码之前先大致了解一下StringBuilder执行过程:
1、刚创建StringBuilder时,底层创建一个字节数组,默认容量为16,值全为0:

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

容量表示最多能装多少,长度表示实际装了多少,这是一个基本概念,别混淆了,接着往下。

2、当我们将"a"、“b”、"c"存入StringBuilder时,实际上存储的是其ASCII码:

97 98 99 0 0 0 0 0 0 0 0 0 0 0 0 0

这时候,容量仍然为16,长度为3。

3、上面显然长度不会超过容量,但是当要添加的字符超过了16个,就会扩容。

扩容:老容量 * 2 + 2

所以如果我们插入a~z共26个字符,容量为34,长度为26。

需要注意的是,老容量依旧为16,但如果插入的字符长度为36,也就是说直接超过了扩容后的大小,这时候容量就会变成实际的36(以实际长度为准)

了解知识后去扒一扒源码:
1、自己new一个StringBuilder对象,Ctrl+左键跟踪,可以看到StringBuilder继承了一个类,空参构造默认是将16传入作为容量的:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
2、跟踪进入super,其创建了长度为16的字节数组:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
3、接着回到上一层,Ctrl+F12查看StringBuilder中的append方法:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
4、跟踪进入其super方法:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
当str为空的时候,会返回appendNull方法,进入appendNull可以发现,返回了一个"null"的字符串:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
5、Ctrl+Alt+"←"返回上一步,如果传入str不为空,则会将count+str.length()传入ensureCapacityInternal函数:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
count表示当前的容器的长度,则count+len表示需要的最小容量,显然这个函数就是做扩容的。

6、跟踪ensureCapacityInternal函数:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
这里会判断所需最小容量是否比老容量大,是的话说明老容量不够用需要进行扩容。

7、进入newCapacity查看扩容方法:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
coder默认为0,则newLength方法中分别传入的是:

(老容量,需要扩容的大小,老容量+2)

8、跟踪进入newLength方法,可以看到,如果增长的幅度老容量+2来的小,那么新容量就是老容量 + 老容量 + 22 * 老容量 + 2,否则就是老容量+增长幅度也就是老容量 + 要新插入的字符串长度
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
剩下的处理就是防止一些异常情况出现,比如扩容后超出了SOFT_MAX_ARRAY_LENGTH的时候,那么就返回SOFT_MAX_ARRAY_LENGTH,一般情况也遇不到。

9、Ctrl+Alt+"←"返回,很直观的可以看到容量length是有上限的,不能超过int数的最大值:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
10、继续返回,可以知道不仅要扩容,还要把之前容器中的值拷贝到扩容后的数组中去,这个底层想看就自己扒一扒:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
11、继续返回,找到putStringAt,看函数名就大致能知道,这个函数的意思是在count位置再添加str:
JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理,JAVASE进阶,java,StringBuilder,字符串,jvm,面试
最后修改一下count值就可以返回这个容器了。文章来源地址https://www.toymoban.com/news/detail-829264.html

到了这里,关于JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++ 字符串拼接

    第一种方法直接是字符串之间相加 第二种方法使用append append 可以设置参数  例如 append(“s1”,3) 这就是把s1前三位拼接 例如下面的就是把adbc的前2位,也就是ab 拼接到s1 上面 打印结果   append(s1,2,4)设置2个参数,就是从第2位开始后面的四位拼接 下面的就是abcdefg从第

    2024年02月16日
    浏览(44)
  • SQL字符串拼接

    ①CONCAT() 拼接字符串基本型: SELECT CONCAT ( lastname , firstname) AS student_name FROM kalacloud_student; ②使用空格拼接字符串: 仍然使用 SELECT CONCAT() 命令,空格需要使用\\\' \\\' 两个单引号引起来。SELECT CONCAT(firstname, \\\' \\\', subject) AS studentDetail FROM kalacloud_student; ③使用特殊符号拼接字符串: S

    2024年02月17日
    浏览(46)
  • Java中字符串占位替换、字符串拼接、字符串与Collection互转的方式

    1、String.format 最原始最基础的方式。 使用%来表示占位,后面跟上不同的标识符,用于限定这个占位符的参数类型,由jdk原生提供支持。 示例: 2、MessageFormat 如果遇到一个参数需要替换模板中多个占位的场景,更友好的方式是MessageFormat,由jdk原生提供支持。 示例: 举例:将

    2024年02月16日
    浏览(57)
  • 【C#】字符串拼接相关

    目录 1.字符串拼接方式1 用+号进行字符串拼接 复合运算符 += 2.字符串拼接方式2 3.控制台打印拼 4.例子  之前的算数运算符 只是用来数值类型变量进行数学运算的 而 string 不存在算数运算符 不能计算 但是可以通过+号来进行字符串拼接 用+号进行字符串拼接 复合运算符 += 按数

    2024年02月04日
    浏览(41)
  • Java拼接list字符串

    使用Stream方法将列表转换为一个流。在流中,使用Collectors.joining方法将所有元素连接成一个字符串。这个方法可以接受一个字符串参数,该参数用作分隔符。 在调用钉钉的一个消息发送API时遇到了一个这样的问题,当时我是这么写的总觉得没问题,但是一直报错,说我userI

    2024年02月16日
    浏览(48)
  • Java Stream 拼接字符串

    一、使用背景                   在开发过程,有时候需要将集合结果转化成字符串,按照指定的字符分隔进行拼接,java stream的收集器提供相应的API,操作简单方便,下面直接上示例代码; 二、API操作

    2024年04月12日
    浏览(43)
  • Godot 4 源码分析 - 增加格式化字符串功能

    Godot 4的主要字符串类型为String,已经设计得比较完善了,但有一个问题,格式化这块没怎么考虑。 String中有一个format函数,但这个函数只有两个参数,这咋用? 查找使用例子,都是这种效果 一看就懵。哪里有之前用的带%s %d...之类的格式化用得舒服。 动手实现一个 提供s

    2024年02月14日
    浏览(46)
  • Sql 函数传递参数 字符串拼接

    使用场景 一个计算价格的函数,多个存储过程调用,因业务需求经常要新增参数,避免修改函数时程序执行存储过程报错,将多个参数拼接为一个字符串传递

    2024年02月10日
    浏览(49)
  • 【Python 千题 —— 基础篇】字符串拼接

    题目描述 我们在编程过程中经常会遇到把不同字符串拼接在一起的情况,从而更直观地展示给用户我们所要表达的信息。本题将给出两个字符串,请依次将这两个字符串拼接在一起。 输入描述 输入两个字符串,用回车分开。 输出描述 程序将输入的两个字符串依次拼接在一

    2024年02月01日
    浏览(50)
  • 字符串拼接操作的面试题讲解

    常量与常量的拼接结果在常量池,原理是编译期优化。String  s1=\\\"a\\\"+\\\"b\\\"+\\\"c\\\"; 常量池中不会存在相同内容的常量。 只要其中有一个是变量 ,结果就在 堆 中。变量拼接的原理是StringBuilder。 如果拼接符号的前后出现了变量,则相当于在堆空间中new String(),具体的内容为拼接的

    2024年02月12日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包