我们都知道在Java中,String对象有两种初始化方式:一种是直接进行字符串的赋值;另一种是通过new 关键字方式初始化。他们两者之间到底有什么区别呢?字符串常量池又是什么东西呢?
String对象的初始化方式
方式一:
String str1 = "Hello";//直接赋值实例化,入池保存
此时我们来分析一下,该过程的内存模型如下:
通过直接赋值的方式为String类对象实例化时,会在栈上生成对象的引用str,并会在堆上的字符串常量池上开辟一块内存,用来存放字符串常量。如果该字符串常量已经存在,下次就不会在开辟新的内存空间用来存储该常量,而是直接将新的对象引用指向该字符串在常量池里的位置。
类似于下面这种情况:
String str1 = "Hello";//直接赋值实例化,入池保存
String str2 = "Hello";//常量池已存在,自动引用
str2就会直接引用str1在实例化时在字符串常量池创建的字符串对象。其内存模型如下:
方式二:
String str = new String("Hello");//new关键字构造方法实例化
由于使用关键字new就一定以为着会开辟新的堆内存空间,这块空间就是为了传入构造方法的字符串数据!
下面分析一下使用构造方法实例化的内存模型:
因为每个字符串都是String类的匿名对象*,所以字符串池中没有“Hello”字符串的话,会在常量池中新创建该对象,其*只负责实例化新的String类对象。而后用关键字new开辟另一块堆空间,将栈引用指向该新创建的String对象。而常量池中的“Hello”常量,如过内有其他引用指向就会成为垃圾空间,等待被JVM的GC回收。
总结:使用new关键字创建,会开辟两块内存空间,不仅会浪费内存,而且实际的内容总是存在堆上新开辟的非池空间上,常量池的空间会成为垃圾空间等待回收。
什么是字符串常量池
Java为了避免产生大量的字符串对象,设计了一个字符串池(String Pool)其本质是一个动态对象数组,通过直接赋值方式初始化创建的字符串对象都会存在于字符串池中,且字符串池中的字符串不会重复,以便可以被共享使用,提高存储效率。在Java中的常量池其实分两种。
1)静态常量池:
是指程序(.class)文件在加载时候会将程序中保存的字符串、普通的常量、类和方法等信息,全部进行分配。
2) 运行时常量池:
是指当一个和程序(.class)加载之后,有一些字符串内容是通过String对象的形式保存后在实现字符串连接处理,由于String对象的内容可以改变,所以此称为运行时常量池。
举个栗子:静态常量池
String strA = "HelloWorld";//开辟新对象并入池
String strB = "Hello"+"World";//使用+进行字符串连接,由于所有都是常量,实际上表示一个字符串
System.out.println(strA==strB);//判断结果:true
举个栗子:运行时常量池
String strA = "HelloWorld";//开辟新对象并入池
String strB = "World";
String strC = "Hello"+strB;
//使用+进行字符串连接,动态拼凑时,由于strB是变量其内容运行时可能改变,所以其无法从常量池获取字符串引用
System.out.println(strA==strC);//判断结果:false
字符串常量池位置
JDK6.0及之前版本:
字符串常量池是放在Perm Gen区(也就是方法区)中;
在JDK7.0版本:
字符串常量池被移到了堆中了。
小思考
留下一些判断供大家思考,有疑惑的可以留言交流。
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // true
System.out.println(s1 == s4); // false
System.out.println(s1 == s9); // false
System.out.println(s4 == s5); // false
System.out.println(s1 == s6); // true