我最近看了挺多关于String相加的博文,总觉得都在告诉大家一些结果,或者公认的表面东西,没有人讲为什么这样。所以这篇博文旨在由浅入深的讲讲String类型的相加的一些知识。
对String类型做一个基本的介绍:
String 是典型的Immutable类,被声明成为final class,所有的属性也是final的。由于它的不可变性,类似拼接、裁剪字符串等动作,都会产生新的String对象。
String原生的保证了基础线程安全,由于可不变,对象在拷贝时不需要额外复制数据。
这些大家在平常的使用过程中都有自己清楚的认识。

其实字符串拼接无非总体来说就是俩种方式:

字符串相加,其实就是字符串的拼接,在字符串拼接时,编译器会先检查拼接字符串是否都是常量,如果是常量,会直接应用常量,编译器并创建一个新的对象来存放对于拼接的字符串的引用。
如果不是常量,其实在编译器中执行的就是StringBuilder.append()方法对字符串进行拼接,然后将拼接好的StringBuilder进行.toString(),得到字符串后返回。

先来说说创建字符串在堆栈中创建的对象是多少个,我先举几个例子:

         String s1 = "a";String s2 = "b";String s3 = new String("123");String s4 = "1" + "2";String s5 = new String("123") + new String("321");String s6 = s1 + s2;

在上面的代码中一共有六行代码,其中行、行2在内存中分别创建了一个对象,这个容易理解,也是基础。接下来看行3,行3创建了几个对象呢?看着代码表面来说,应该是一个对象,但是真的是这样吗?
先给大家贴一下new String()的构造代码(运行环境是jdk1.8),如下:

public String(String original) {this.value = original.value;this.hash = original.hash;
}

很简单,就是一个有参构造器,将传入的值构造一个新的String对象,并且根据传入的值做hash,好这段源代码告诉我们new String是只创建一个对象的。但是,我们看看这段代码反编译后的结果:

这是行3进行编译,然后反编译的结果,从结果中我们可以看到操作顺序,和创建的对象,一个是 new 出来的对象,也就是new String(),另一个是 String 123,从这里不难看出,在jdk的运行操作中,底层是出现了俩个String对象的,没有额外的对象生成出来(当然不能算String底层的 char[] 数组)。那么也就可以看出来,在编译器运行行3的代码时,实际上是创建了俩个对象的,一个是s3,一个是“123”。
好,我们接着看行4,行4是一个字符串的拼接,如果按照目前为止的理解方式,那么应该在内存中创建的是3个对象,“1,”,“2”和“12”,但是并不是这样子的。这里涉及到一个重要的知识点(个人的理解总结):

如果在创建String的时候引用的是字符串常量(jdk1.8之前字符串常量是单独的一块内存空间,java 9之后,字符串常量池被移动到了堆中),那么只会创建当前对象

该怎么理解这句话呢,首先“1”,和“2”在java中,类似于这样的,我们统一称为字符串常量,在创建新的字符串时,编译器会先去访问常量池,因为“1”,“2”在字符串常量池中存在,所以,在创建s4时,会直接引用字符串常量池中的“1”和“2”,并将合并的结果创建一个新的对象s4,如下图反编译后的结果:

可以看到反编译后只是创建了一个String对象 “12”。

那么有部分“彭于晏”(同学)会问,那么对于行3你怎么解释?这里要说一下,行3 执行的是new String操作,在java8中,注意一下这个操作的顺序,还有反编译后的结果显示。

接下来要说的是行6,(不是行5哦),行6是两个字符串的拼接,这是咱们常用的拼接方式(以后在实际开发中尽量直接使用StringBuilder去拼接字符串),那么这是创建了几个对象呢?首先s1对象已经创建了,s2对象也已经创建了,当已经创建好字符串对象时,那么再做字符串拼接时,编译器会直接饮用创建好的对象地址,也就是s1和s2的对象地址,将s1和s2对象都拿出来时,此时还没有创建新的对象,接下来是字符串拼接,将俩个对象合成一个新的对象,那么这个新的对象就是创建出来的对象。那这步操作只是引用后生成了一个对象吗?这样描述是不准确的。我们先来看看反编译之后的结果:

从图中不难看出,当编译器监测到俩个String对象要进行拼接时,首先会创建一个StringBuilder,然后会逐步按照顺序将俩个对象append到StringBuilder中,最后再执行toString方法。
那么这其中的整个流程到底创建了多少个对象呢?我按照执行顺序将源代码放出来。
首先StringBuilder的类的继承和实现

public final class StringBuilderextends AbstractStringBuilderimplements java.io.Serializable, CharSequence
{
......//此处省略一完字
}

从反编译的结果中看出是逐步append(),而且参数是String,所以调用的数StringBuilder的append(String str)方法,源代码如下:

@Overridepublic StringBuilder append(String str) {super.append(str);return this;}

调用了父类的append方法,代码如下:

public AbstractStringBuilder append(String str) {if (str == null)return appendNull();int len = str.length();ensureCapacityInternal(count + len);str.getChars(0, len, value, count);count += len;return this;}

按照执行顺序,先查看appendNull()方法,源代码如下:

private AbstractStringBuilder appendNull() {int c = count;ensureCapacityInternal(c + 4);final char[] value = this.value;value[c++] = 'n';value[c++] = 'u';value[c++] = 'l';value[c++] = 'l';count = c;return this;}

内部方法 ensureCapacityInternal(c + 4),源代码如下:

 private void ensureCapacityInternal(int minimumCapacity) {// overflow-conscious codeif (minimumCapacity - value.length > 0) {value = Arrays.copyOf(value,newCapacity(minimumCapacity));}}

内部方法newCapacity(minimumCapacity),源代码如下:

private int newCapacity(int minCapacity) {// overflow-conscious codeint newCapacity = (value.length << 1) + 2;if (newCapacity - minCapacity < 0) {newCapacity = minCapacity;}return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)? hugeCapacity(minCapacity): newCapacity;}

因为父类的方法又调用了一遍ensureCapacityInternal(count + len)方法,所以从源代码中看到,至此appen方法是没有新增对象的(Arrays.copyOf()方法是不会复制对象的,复制的是引用地址)。再接着看反编译后的结果,两个append()方法都没有新增对象,那么最后一步toString()方法会吗?我们看源代码,如下:

@Overridepublic String toString() {// Create a copy, don't share the arrayreturn new String(value, 0, count);}

从代码里看到是有个String的构造器的,那么这里会创建一个String的对象,其实从代码注释Create a copy, don't share the array也能看出来。
执行完toString()方法后,程序也就执行完这一部分的代码了,所以这整个过程一共创建了两个对象,一个是StringBuilder,一个是String,所以行6 一共创建的是俩个对象,一个String对象。

接下来要说的就是行5,行5 略有些复杂,首先看表面,按照之前的分析,应该是6个对象“123”、”321“、俩个new String()动作、s5,还有一个StringBuilder对象,这样才能验证上面所说的。我将反编译后的结果放上来:


按照步骤来看,首先创建一个StringBuilder对象,然后创建“123”String对象,再创建new String(),执行append(),接着创建“321”String对象,再创建new String(),执行append(),最后执行toString方法。结合上面的经验,最后得出创建了6个对象,分别是StringBuilder对象,两个new String()对象,”123“,”321“,toString()方法创建的String对象

还有几个冷知识,有兴趣的可以了解一下:

字符串的hashcode值取决于内容,而不是地址;
string.intern()方法,会将一个字符串的引用放到常量池里。
例如:
String s3 = new String("12") + new String("34");
s3.intern();
String s4 = "1234";
System.out.println(s3 == s4);
如果有行2代码,结果是true,如果不加行2代码结果就是false。

综上所述,对于String我们有了一个大概的认识,String在创建时一共创建了 几个对象,几个String对象,我们大概心理有了清晰的认识,关于字符串的拼接,其实就是StringBuilder的append(),所以在日后的工作中也好,学习中也好,如果能使用的StringBuilder尽量使用StringBuilder,String的字符串直接拼接会造成多余内存的消耗(一般来说使用+号也没什么大碍)。

关于String字符串和字符串相加(拼接)的一些知识相关推荐

  1. .NET 6 使用 string.Create 提升字符串创建和拼接性能

    本文告诉大家,在 dotnet 6 或更高版本的 dotnet 里,如何使用 string.Create 提升字符串创建和拼接的性能,减少拼接字符串时,需要额外申请的内存,从而减少内存回收压力 本文也 ...

  2. java 创建string对象机制 字符串缓冲池 字符串拼接机制 字符串中intern()方法...

    字符串常量池:字符串常量池在方法区中 为了优化空间,为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池.如果字符串已经存在池中,就 ...

  3. c++语言怎么实现字符串拼接,C++ string类和字符串的访问和拼接操作

    C++ 增强了对字符串的支持,除了可以使用c中的字符串,还可以使用内置的数据类型string,string类处理字符串会翻遍很多,完全可以代替C语言中的char 数组和char 指针. 使用sting ...

  4. C++ string类和字符串的访问和拼接操作

    2019独角兽企业重金招聘Python工程师标准>>> 转载请标明出处: http://blog.csdn.net/u011974987/article/details/525044 ...

  5. java 创建string对象机制 字符串缓冲池 字符串拼接机制

    对于创建String对象的机制,在这一过程中涉及的东西还是值得探究一番的. 首先看通过new String对象和直接赋值的方式有什么区别,看如下代码: public static void main( ...

  6. js字符串与数字相加减

    var a='123'; var b=a+1; console.log(b); 输出为1231,type是string var a='123'; var b=a-1; console.log(b); ...

  7. 【Flutter】Dart 数据类型 字符串类型 ( 字符串定义 | 字符串拼接 | 字符串 API 调用 )

    文章目录 I . 字符串定义 I . 字符串拼接 III . 字符串 API 调用 IV . 字符串 Demo 示例 I . 字符串定义 使用单引号 ' ' 和 双引号 " " 都 ...

  8. [Leetcode]第[43]题[JAVA][字符串相乘][字符串相加]

    [问题描述][中等] [解答思路] 1. 普通竖式 **复杂度:O(N^2) ** class Solution {/*** 计算形式* num1* x num2* ------* result*/p ...

  9. Java中String类、字符串常量池、字符串常用方法

    String类: String代表字符串类,java中所有双引号中的内容都称为字符串,如:"hello".字符串是不可改变的,因此字符串是可以共享使用的,相当于char字符数组,但 ...

  10. 常用类 (六) ----- String类与字符串

    相关文章: <常用类 (一) ----- Arrays数组工具类> <常用类 (二) ----- Math类> <常用类 (三) ----- BigDecimal和Big ...

最新文章

  1. JSP与ASP的比较
  2. 2018年10月自考java_请注意!2018年自考《Java语言程序设计(一)》课程全国统一命题考试...
  3. python3.6.3安装-CentOS7.2安装Python3.6.3
  4. Netty详解(二)Linux 网络IO模型
  5. 超级账本(Hyperledger Fabric):基本架构及运作机制
  6. 机器学习实践四--正则化线性回归 和 偏差vs方差
  7. 已经push的如何回退_如何撤回Git push 到远程分支以后的方法
  8. 图片热点的使用,html area 的用法
  9. 【iCore3 双核心板_ uC/OS-III】例程二:任务的建立与删除
  10. scrollTop如何实现click后页面过渡滚动到顶部
  11. 基于android的个人收支财务管理,基于Android的个人财务管理系统的设计与实现.doc...
  12. Oracle 购买价格 和 服务费 计算方式
  13. html引用百度中图片不显示,百度图片不显示怎么办 百度图片不显示解决方法
  14. HR问:“对我们公司你有什么问题要问的吗”,怎样回答才算完美!
  15. 【阿里云IoT+YF3300】2.阿里云IoT云端通信Alink协议介绍
  16. ZLMediaKit中_all_track_ready置为true的过程
  17. 汽车SoC芯片IP供应商
  18. linux运行luminati,Luminati使用从入门到精通-Luminati中国
  19. Java String.contains()方法
  20. fastcgi php 集群 分离,使用nginx配置多个php fastcgi负载均衡--梦飞翔的地方(梦翔天空)...

热门文章

  1. 个人私人微信号批量化是一座金矿
  2. 在next主题添加微信公众号二维码
  3. webgis入门实战
  4. NJM2507RB1 差分传输接收器芯片
  5. 【Amber】分子动力学结果分析(一)mdout_analyzer.py
  6. echart 地图数据封装
  7. java灰度发布系统_灰度发布系统架构设计
  8. 在WINDOWS上开发IOS应用的方法
  9. Elasticsearch 的新 range 丰富策略使上下文数据分析更上一层楼 - 7.16
  10. 位宽512bit显卡_6144 CUDA/512bit位宽 Maxwell架构曝光