600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 关于String字符串和字符串相加(拼接)的一些知识

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

时间:2019-01-15 16:31:15

相关推荐

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

我最近看了挺多关于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的字符串直接拼接会造成多余内存的消耗(一般来说使用+号也没什么大碍)。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。