String字符串的存储原理(学习笔记)
关于Java JDK 中内置的一个类:java.lang.String
1.String表示字符串类型,是引用数据类型,不是基本的数据类型。
2.使用双引号括起来的都是String对象。比如:“abc”,“java”,这就是两个String对象。
3.String 类是不可变类,也就是说 String 声明对象以后,将不可修改,“abc"从出生到死亡,不可能变成"abcd”,“ab”。
4.Java中,使用双引号括起来的字符串,都直接存储在"方法区"的“字符串常量池中”。
这么做的原因:字符串在实际的开发中使用的太频繁,为了执行效率,所以把字符串放到了方法区的字符串常量池当中。
5.除了双引号的构造方法,还可以通过new的方式来创建字符串对象,如使用:
String s3=new String("xy");
来创建字符串对象。
两种构造方式有何不同,用以下例子来说明:
例1.用双引号创建字符串对象
public class StringTest01{
public static void main(String[] args){
String s1 ="abcdef";
String s2="abcdef"+"xy";
}
}
来画一下这个程序的内存图:
- 大框表示JVM虚拟机。里面分为三个部分,方法区,堆和栈。
2. 首先会在方法区进行类加载,包括StringTest01.class,String.class,代码片段,静态变量等。
3. 执行main方法,main方法会压栈,生成main方法栈帧。
4. 执行main方法的第一句,在方法区的字符串常量池生成String对象"abcdef",其内存地址,比如0x1234。
5. 将字符串"abcdef"的内存地址0x1234赋值给了s1,String s1表示一个引用,s1这个局部变量中存放的是"abcd"的内存地址,而不是"abcd"字符串本身。所以在mian方法栈中出现了一个局部变量s1,存储"abcdef"的内存地址0x1234。
- 执行main方法的第二句,第二句是一个字符串的拼接
String s2="abcdef"+"xy";
因为"abcdef"已经在字符串常量池中有了,所以不用再新建,那么会在常量池中创建字符串对象“xy”。
但是注意:此时没有一个引用指向字符串对象"xy"!
- 由于拼接的操作,在字符串常量池中,字符串对象“abcdef”和字符串对象“xy”进行拼接,生成了新的字符串对象“abcdefxy”,假设它的内存地址是0x2356。
- 将字符串"abcdefxy"的内存地址0x2356赋值给了s2,String s2表示一个引用,s2这个局部变量中存放的是"abcdefxy"的内存地址,而不是"abcdefxy"字符串本身。所以在mian方法栈中出现了一个局部变量s2,存储"abcdefxy"的内存地址0x2356。
结论:这两行代码最终创建了3个字符串对象,都存放在字符串常量池中,栈帧中的两个变量存放的是字符串在字符串常量池的内存地址。
例2:用new方式创建字符串对象
我们在例1的基础上创建s3:
public class StringTest01{
public static void main(String[] args){
String s1 ="abcdef";
String s2="abcdef"+"xy";
String s3 = new String("xy");
}
}
分析:使用new的方式来创建字符串对象,这里的"xy"是从哪里来的?
首先,双引号括起来的都在字符串常量池中有一份。
其次当我们new一个对象的时候,是在堆内存中开辟空间。
所以在堆内存中创建了一个String对象,里面存放了字符串对象"xy"的内存地址(假设0x5656)。
最后在main方法栈帧中,出现了String s3这个变量,保存了String对象的内存地址(假设String对象的堆内存地址是0x1111)。
结论:如果采用new的方式,在==mian方法栈帧中创建的变量,存储String对象的堆内存地址;==堆内存的String对象则存储字符串在字符串常量池的内存地址</