● 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new出来的对象)或者常量池中(字符串常量对象存放在常量池中。)
● 堆:存放所有new出来的对象。
● 常量池:存放字符串常量和基本类型常量(public static final)。
字符串常量池既不在堆中也不在栈中,是独立的内存空间管理(方法区中)。
对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等(仅仅是值相等)的字符串,在常量池中永远只有一份,在堆中有多份。
String创建对象的两种方式
(1) String str = "abc";
创建对象的过程:
① 首先在常量池中查找是否存在内容为"abc"字符串对象
② 如果不存在则在常量池中创建"abc",并让str引用该对象
③ 如果存在则直接让str引用该对象
(2) String str = new String("abc");
创建实例的过程:
① 首先在堆中(不是常量池)创建一个指定的对象"abc",并让str引用指向该对象
② 在字符串常量池中查看,是否存在内容为"abc"字符串对象
③ 若存在,则将new出来的字符串对象与字符串常量池中的对象联系起来
④ 若不存在,则在字符串常量池中创建一个内容为"abc"的字符串对象,并将堆中的对象与之联系起来
intern() 方法可以返回该字符串在常量池中的对象的引用
代码测试:
public class StringTest {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "ab";
String s3 = "c";
String s4 = "ab" + "c";
String s5 = s2 + s3;
String s6 = new String("abc");
String s7 = s6.intern();
System.out.println(s1 == s4); // ①
System.out.println(s1 == s5); // ②
System.out.println(s1 == s6); // ③
System.out.println(s1 == s7); // ④
System.out.println(s6 == s7); // ⑤
}
}
结果分析:
① true
因为String s4 = "ab" + "c";会先查找常量池中是否存在内容为"abc"的字符串对象,若存在则直接让s4引用该对象,显然String s1 = "abc";会在常量池中创建"abc"对象,所以s1引用该对象,s4也引用该对象,所以S1 == S4为true。
② false
因为String s5 = s2 + s3;涉及到变量的相加,会产生新的对象,其内部实现是先new一个StringBuilder,然后append(s2),append(s3),然后让s5引用toString()返回的对象。(若想了解更多细节,可以查看反编译的代码)。
③ false
String s1 = "abc";------在常量池中建立一个"abc",s1指向"abc";引用s1存储的是"abc"在常量池的地址。
String s6 = new String("abc");------在堆上分配内存,引用s6存储的是new String("abc") 在堆上的内存地址。
还有一点区别是s1在类加载时就完成了初始化,而s6 要在执行引擎执行到那一行代码时才完成初始化。
④ true
String s7 = s6.intern();中intern 方法返回s6在常量池中的对象的引用,在字符串常量池中查看,是否存在与字符串s6的内容"abc"相等的字符串对象,此处已经存在String s1 = "abc";已经存在内容为"abc"的字符串对象。
⑤ false
此处String s7 = s6.intern();等同于String s7 = "abc";
与③同理。
● 堆:存放所有new出来的对象。
● 常量池:存放字符串常量和基本类型常量(public static final)。
字符串常量池既不在堆中也不在栈中,是独立的内存空间管理(方法区中)。
对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等(仅仅是值相等)的字符串,在常量池中永远只有一份,在堆中有多份。
String创建对象的两种方式
(1) String str = "abc";
创建对象的过程:
① 首先在常量池中查找是否存在内容为"abc"字符串对象
② 如果不存在则在常量池中创建"abc",并让str引用该对象
③ 如果存在则直接让str引用该对象
(2) String str = new String("abc");
创建实例的过程:
① 首先在堆中(不是常量池)创建一个指定的对象"abc",并让str引用指向该对象
② 在字符串常量池中查看,是否存在内容为"abc"字符串对象
③ 若存在,则将new出来的字符串对象与字符串常量池中的对象联系起来
④ 若不存在,则在字符串常量池中创建一个内容为"abc"的字符串对象,并将堆中的对象与之联系起来
intern() 方法可以返回该字符串在常量池中的对象的引用
代码测试:
public class StringTest {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "ab";
String s3 = "c";
String s4 = "ab" + "c";
String s5 = s2 + s3;
String s6 = new String("abc");
String s7 = s6.intern();
System.out.println(s1 == s4); // ①
System.out.println(s1 == s5); // ②
System.out.println(s1 == s6); // ③
System.out.println(s1 == s7); // ④
System.out.println(s6 == s7); // ⑤
}
}
结果分析:
① true
因为String s4 = "ab" + "c";会先查找常量池中是否存在内容为"abc"的字符串对象,若存在则直接让s4引用该对象,显然String s1 = "abc";会在常量池中创建"abc"对象,所以s1引用该对象,s4也引用该对象,所以S1 == S4为true。
② false
因为String s5 = s2 + s3;涉及到变量的相加,会产生新的对象,其内部实现是先new一个StringBuilder,然后append(s2),append(s3),然后让s5引用toString()返回的对象。(若想了解更多细节,可以查看反编译的代码)。
③ false
String s1 = "abc";------在常量池中建立一个"abc",s1指向"abc";引用s1存储的是"abc"在常量池的地址。
String s6 = new String("abc");------在堆上分配内存,引用s6存储的是new String("abc") 在堆上的内存地址。
还有一点区别是s1在类加载时就完成了初始化,而s6 要在执行引擎执行到那一行代码时才完成初始化。
④ true
String s7 = s6.intern();中intern 方法返回s6在常量池中的对象的引用,在字符串常量池中查看,是否存在与字符串s6的内容"abc"相等的字符串对象,此处已经存在String s1 = "abc";已经存在内容为"abc"的字符串对象。
⑤ false
此处String s7 = s6.intern();等同于String s7 = "abc";
与③同理。