文章目录
还记得String、StringBuilder、StringBuffer区别、String底层原理、实例化、拼接、比较吗?如果忘记可以到这里重温复习 String、StringBuilder、StringBuffer区别;String底层详解,实例化、拼接、比较;String为什么不可变
1.String str1="abc"和String str2=new String(“abc”)区别
有两种方式创建String对象:字面量赋值、new关键字
-
使用字符串字面值创建String对象,如String str = “abc”:java中有个字符串常量池,当创建一个字面量字符串时,JVM首先检查字符串常量池中是否已经存在该字符串,如果存在 则直接返回字符串对象的引用,否则就创建一个新的字符串对象并放入字符串常量池中,最终将该对象的引用赋值给变量str。引用str指向常量池中字符串"abc"的地址,是在常量池中拿值【字符串常量池中不会存储相同内容的字符串】
-
使用构造函数构建String对象,如String str = new String(“abc”):通过new关键字创建字符串对象,会先检查字符串常量池中是否有相同的字符串,如果有 则拷贝一份放到堆中,然后返回堆中地址;如果没有 就先在字符串常量池中创建"abc"这个字符串,而后再复制一份放到堆中 并把堆地址返回给str。即最终字符串常量池和堆内存都会有这个对象,最后返回的是堆内存的对象引用
只要使用new方法,不管字符串常量池中是否存在"abc",都会在堆中创建新的对象(注意 和字符串常量池中的"abc"相区分),方式一的效率高于方式二
由于new关键字会在堆中开辟空间,因此开发中一般不建议使用,直接用字面量形式赋值即可。
2.String str=“abc”,String str=new String(“abc”)创建了几个对象
经过上文讲解,我们就知道两者区别在于 创建对象个数不同
- String str=“abc"创建了几个对象? 0个 或 1个。如果字符串常量池中没有"abc”,则在常量池中创建"abc" 并让str引用指向该对象(1个);如果字符串常量池中有"abc",则一个都不创建 直接返回地址值给str(0个)
- String str=new String(“abc”)创建了几个对象? 1个 或 2个。如果字符串常量池中没有"abc",则在字符串常量池和堆内存中各创建一个对象,返回堆地址(2个,一个是堆中new的对象,另一个是char[]对应的常量池中数据"abc");如果常量池中有"abc",则只在堆中创建对象并返回地址值给str(1个)。【new相当于在堆中新建了value值,每new一个对象就会在堆中新建,地址值也因此不同,堆中的value存储着指向常量池的引用地址】
3.String str =“ab”+ “cd”, String str =new String(“ab”) + new String(“cd”) 会创建几个对象
在Java中从".java"文件编译成".class"文件,会有一个优化器去优化我们的代码。
- String str =“ab”+ “cd”:0个或1个。常量相加,经过编译器优化成了String str =“abcd”。如果字符串常量池中没有"abcd”,则在常量池中创建"abcd" 并让str引用指向该对象(1个);如果字符串常量池中有"abcd",则一个都不创建 直接返回地址值给str(0个)
- String str1=“ab”, String str2=“cd”, String str=str1+str2:String str=str1+str2 至多创建3个对象(String str1=“ab”、str2="cd"没有计入)。变量相加,经过编译器优化成StringBuilder,底层实现为 (new StringBuilder()).append(a).append(b).toString(),new StringBuilder()底层调用new char[capacity],append后调用StringBuilder的toString()进行类型转换,实则为new String(“abcd”)。三个对象分别为——new StringBuilder()、new String(value,0,count)、“abcd”
public class StringExercise05 {
public static void main(String[] args) {
String a = "ab"; //创建a对象
String b = "cd"; //创建b对象
//解读:先创建一个StringBuilder sb = new StringBuilder();执行 sb.append(a);执行sb.append(b);String c = sb.toString();
//等价于 (new StringBuilder()).append(a).append(b).toString()
//最后其实是 c 指向堆中对象的(String) value[]->池中 "abcd"
String c = a + b;
String d = "abcd";
System.out.println(c == d); //false
String e = "ab" + "cd";
System.out.println(d == e);//true
}
}
一共创建3个对象,内存布局图如下
查看源码 StringBuilder构造方法、append方法、
//StringBuilder构造方法
public StringBuilder() {
super(16);
}
//追溯到父类AbstractStringBuilder的构造方法,分配一个长度为16的char数组
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
//StringBuilder的append方法
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
//追溯到父类AbstractStringBuilder的append方法
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count); //getChars方法见下
count += len;
return this;
}
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
//我们的代码没有超过16个,故不会出现扩展value的情况。append使用arraycopy的复制方法,也没有产生新的对象,而StringBuilder的toString()方法通过前面数组new了一个新String
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
- String str =new String(“ab”) + “cd”:至多5个。分别为 new StringBuilder()、new String(“ab”)、常量池中的"ab"、StringBuilder 的 toString() 调用 new String(“abcd”)、常量池中的"abcd"
- String str =new String(“ab”) + new String(“cd”):至多7个。分别为 new StringBuilder()、new String(“ab”)、常量池中的"ab"、new String(“cd”)、常量池中的"cd"、StringBuilder 的 toString() 调用 new String(“abcd”)、常量池中的"abcd"
注意:
1)new String(“ab”)时,如果常量池中已有"ab",则不会在常量池中新建"ab",因为在常量池中只能存在一份相同的对象,即字符串常量池中不会存储相同内容的字符串
2)StringBuilder的toString(),在字符串常量池中,没有生成"ab"。如果前后文还有代码、并且字符串常量已在常量池中存在时,相同的常量将不会再创建,故至多6个对象。
参考 https://blog.csdn.net/xiaojin21cen/article/details/106404531
4.String a=“abc”,String b=new String(“abc”),String c=“ab”+"c"比较
上文已介绍三种方式的原理
String a = "abc"; //字符串常量池
String b = "abc";
String c = new String("abc"); //new创建对象,堆和常量池中都会有该对象
String c1 = new String("abc");
String d = "ab" + "c"; //常量与常量拼接,结果在常量池中。查找常量池中是否存在"abc",如果存在 则让d直接引用
String e = new String("ab") + new String("c");
//a、b、c、c1、d、e 使用equals比较,由于字符串内容都相等 所以均会返回true
//故此处重点使用==,观察是否为同一个对象
System.out.println("a==b:" + (a==b)); //true
System.out.println("c==c1:" + (c==c1)); //false
System.out.println("a==c:" + (a==c)); //false
System.out.println("a==d:" + (a==d)); //true
System.out.println("c==d:" + (c==d)); //false
System.out.println("a==e:" + (a==e)); //false
System.out.println("c==e:" + (c==e)); //false
System.out.println("d==e:" + (d==e)); //false
String str1 = "ab";
String str2 = "cd";
String str3 = "ab" + "cd";
String str4 = "abcd";
System.out.println("str3==str4:" + (str3==str4)); //true
//内部实现 String temp = (new StringBuilder()).append(str1).append("cd").toString();
String str5 = str1 + "cd";
System.out.println("str5==str4:" + (str5==str4)); //false
//内部实现 String temp1 = (new StringBuilder()).append(str1).append(str2).toString();
String str6 = str1 + str2;
System.out.println("str6==str4:" + (str6==str4)); //false
str5 = str5.intern(); //将str5放进常量池,并将引用赋给原来的str5
System.out.println("str5==str4:" + (str5==str4)); //true
- String str1 =“ab”+“cd”:常量 与 常量 的拼接结果在 常量池,原理是 编译期 优化;常量池中不会存在相同内容的常量;
- String str2 = str1+“ef”, String str6 = str1+str2:只要其中一个是变量(运行时才能知道),结果就在堆中。变量拼接的原理 是StringBuilder 【只要有变量参与 地址就指向堆中数据】
- 如果拼接的结果调用 intern() 方法,则主动将常量池中 还没有的字符串对象放入池中,并返回地址。