参考笔记:
Java String 类深度解析:内存模型、常量池与核心机制_java stringx、-CSDN博客
解析java中String的内存原理_string s1 = new string("ab");内存分析-CSDN博客
目录
1.String初识
2.字符串字面量
3.内存原理图
4. 示例验证
4.1 字面量直接赋值
4.2 new方式赋值
4.3 new和直接赋值混合
4.4 字符串拼接
4.4.1 +号两边至少有一个变量
4.4.2 +号两边都是字面量
4.5 intern()方法
4.5.1 示例代码1
4.5.2 示例代码2
1.String初识
(1)Java 的 String 是引用数据类型
(2)因为字符串使用比较频繁,所以 Java 专门为字符串准备了一个字符串常量池。 Java 8 之前字符串常量池在方法区中,Java 8 之后在堆中,本文讲的是 Java 8 之前
(3)放在字符串常量池中的好处就是省去了对象的创建过程,从而提高程序的执行效率。常量池是一种缓存技术,缓存技术是提高程序执行效率的重要手段
(4)字符串一旦创建是不可变的(String 源码有一个属性:private final byte[] value)
示例:String s = "hello"
其中 "hello" 存储在字符串常量池中,字符串常量池中的 "hello" 不可变,不能变成 "hello123"。而 s 仍然可以指向其他的字符串对象,例如 s = "xyz"
2.字符串字面量
字符串字面量:我们自己给出的字符串,也可以称作字符串常量。如 "123","abc"
判断方法:简单来说就是在程序中的任何位置,只要出现带上英文双引号的就可以算是字符串字面量
字符串常量池规则
字符串字面量一旦出现,会先去方法区里的字符串常量池找有没有该字符串常量。
(1)如果有,则直接返回字符串常量池中存放该字符串的空间的地址
(2)如果没有,则在字符串常量池里面开辟一块空间用来存放该字符串常量,并返回空间地址
示例代码
public class demo {
public static void main(String[] args) {
String s1 = "123";
String s2 = new String("456");//"456"
String s3 = "12";
String s4 = "k";
String s5 = s3+s4;//"12K"
String s6 = s3+"马";//"12马"
String s7 = "s"+"abc";//"sabc"
}
}
经过上述代码,字符串常量池中有字符串:"123","456","12","k","马","s","abc","sabc"
这些都是字符串字面量,但是字符串常量池中不会有 "12k" 、"12马"(后面会作解释)
3.内存原理图
4. 示例验证
4.1 字面量直接赋值
注:s1 == s2 比较的是 s1 与 s2 的引用地址是否相同, s1.eauals(s2) 比较的是 s1与 s2 的内容是否相同
示例代码
public class demo {
public static void main(String[] args){
String s1="12";
//字符串字面量12会先在方法区中的字符串常量池中找,
//发现没有同内容的字符串常量,那么就开辟一个新的空间,存放12
//然后再把这个地址赋值给s1
String s2="12";
//字符串字面量12会现在方法区中的字符串常量池中找,
//发现已经存在了字符串常量12了,此时无需再区开辟空间
//只需要把已经存在的字符串常量的地址赋值给s2就行了
//s1与s2指向的是同一个字符串常量的地址,所以s1==s2,输出true
System.out.println(s1==s2);
}
}
示例代码内存原理图
4.2 new方式赋值
注:Java 开发中很少使用 new 的方式给 String 赋值,因为在堆中会产生不必要的内存分配,直接使用字面量赋值更高效
示例代码
public class demo {
public static void main(String[] args) {
String s1 = new String("123");
String s2 = new String("123");
//只要有new就会在堆内存中开辟空间
//字符串字面量在字符串常量池中开辟的空间的那个地址值会存放到开辟的堆内存中
//s1,s2指向的都是自己堆内存中开辟的空间,并没有直接指向字符串常量池的"123"的那个地址
//因此s1与s2进行 == 比较,输出为false
System.out.println(s1 == s2);
//s1与s2内容相同,输出为true
System.out.println(s1.equals(s2));
}
}
示例代码内存原理图
4.3 new和直接赋值混合
示例代码
public class demo {
public static void main(String[] args) {
String s1=new String("123");
String s2="12"+"3";
//会在字符串常量池开辟"123","12","3"的空间,
//"123"在字符串常量池中开辟的空间地址赋值到了s1中开辟的堆空间中,s1指向的是堆空间地址
//"123"在字符串常量池中开辟的空间地址直接赋值给了s2
//因此,s1与s2进行 == 比较,输出为false
System.out.println(s1==s2);
}
}
示例代码内存原理图
4.4 字符串拼接
4.4.1 +号两边至少有一个变量
如果 + 号两边至少有一个是变量,则用 + 拼接生成的新的字符串不会被放到字符串常量池中,只会存放到堆中
示例代码
public class demo {
public static void main(String[] args) {
//字符串常量池中创建"123","456"
String s1 = "123";
String s2 = "456";
//s3="123456"是拼接而来,所以"123456"不在字符串常量池中,存放在堆中
String s3 = s1 + s2;
//字符串常量池中创建"123456"
//s4的引用是字符串常量池中存放"123456"的地址
String s4 = "123456";
//s3与s4的引用不同,所以输出为false
System.out.println(s3 == s4);
//s3与s4的内容相同,输出为true
System.out.println(s3.equals(s4));
}
}
4.4.2 +号两边都是字面量
如果 + 号两边都是字符串字面量(常量),编译器会进行自动优化。在编译阶段进行拼接。 + 号两边的字符串字面量、拼接后的新字符串都会被放到字符串常量池中,返回的引用也是来自字符串常量池
示例代码
public class demo {
public static void main(String[] args) {
//字符串常量池中创建"123"、"456"、"123456"
//返回字符串常量池中存放"123456"的地址
String s1 = "123"+"456";
//字符串常量池中已存在"456"
//返回字符串常量池中存放"456"的地址
String s2 = "456";
//s2与"456"的引用相同,所以输出为true
System.out.println(s2 == "456");
//字符串常量池中已存在"123456"
//返回字符串常量池中存放"123456"的地址
String s3 = "123456";
//s1与s3的引用相同,所以输出为true
System.out.println(s1 == s3);
}
}
示例代码内存原理图
4.5 intern()方法
intern() 检查当前该字符串字面量是否已经存放于字符串常量池中
(1)存在:直接返回字符串常量池中存放该字符串字面量的空间地址
(2)不存在:将新的字符串字面量添加到常量池中,并返回引用
4.5.1 示例代码1
示例代码
public class demo {
public static void main(String[] args) {
String s1 = new String("123");
//在字符串常量池开辟"123"的空间
//"123"在字符串常量池中开辟的空间地址赋值到了s1中开辟的堆空间中,s1指向的是堆空间地址
//字符串常量池中已有"123",调用intern()返回其在字符串常量池中的引用地址
String s2 = new String("123").intern();
//字符串常量池中已有"123",返回其在字符串常量池中的引用地址
String s3 = "123";
System.out.println(s1==s2);//false
System.out.println(s2==s3);//true
System.out.println(s1==s3);//false
}
}
示例代码内存原理图
4.5.2 示例代码2
在 4.4.1 提到,如果 + 号两边至少有一个是变量,则用 + 拼接生成的新字符串不会被放到字符串常量池中,只会存放到堆中
这种场景下就可以用 intern() 方法来将 + 拼接生成的新字符串手动添加到字符串常量池中,并且返回的引用就来自字符串常量池
示例代码
public class demo {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "world";
//拼接生成的"helloworld",存放在堆中
String s3 = s1 + s2;
//手动将拼接生成的"helloworld"添加到字符串常量池中,并返回引用
String s4 = s3.intern();
//字符串常量池中已有"helloworld",返回其在字符串常量池中的引用地址
String s5 = "helloworld";
//输出true
System.out.println(s4==s5);
}
}