常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据,包括了关于类,方法,接口等中的常量,也包括字符串常量。
字符串常量池是一个固定大小的Hashtable,用来存放字符串。
在堆(heap)区分配了一部分内存空间作为常量池区。
String类的使用频率极高。
为了避免JVM 首先创建大量的字符串对象,然后再进行垃圾回收。
以节约内存和提高运行效率。
public class StringTest {
public static void main(String[] args) {
String a = "wechat" + "zhou";
}
}
wechat和 zhou 都是字符串,编译时可知,编译器会自动将这行代码优化为如下代码
public class StringTest {
public static void main(String[] args) {
String a = "wechatzhou";
}
}
(其实是上述主要的生成方法中的第三点,需要使用intern()方法)
引用拼接(如s3)的最终内容,是不会在常量池存在的
String s1=“1”;创建了1个"1"对象,在常量池。
String s2=new String(“1”);创建了两个"1"对象,一个在常量池,一个在heap区。
String s3=“1”+“1”;创建了一个"11"对象,不在常量池,在heap区。
public class StringTest {
public static void main(String[] args) {
String a = "we";
String b = a + "chat";
// 上下含义相同
String c = "moz";
String d = "bad";
String f = c + d;
}
}
由于引用的值在程序编译期是无法确定的
当存在字符串引用(a+"chat"或c+d)的拼接时,Java编译器会创建一个StringBuilder对象,通过append()方法实现拼接。
"wechat"并不会添加到字符串常量池, 但是可以通过b.intern() 方法添加到常量池
效果和字符串拼接一致
public class StringTest {
public static void main(String[] args) {
final String a = "we";
final String b = "chat";
String c = a + b + "handsome";
}
}
用final修饰的字符串就是在编译期可知的,编译期就会将以上代码优化为
public class StringTest {
public static void main(String[] args) {
final String a = "we";
final String b = "chat";
String c= "wechathandsome";
}
}
可以实现常量池中的添加
1 如果常量池中存在当前字符串, 就会直接返回当前字符串.
2 如果常量池中不存在当前字符串, 会将此字符串放入常量池中, 再返回.
1 如果常量池中存在当前字符串, 就会直接返回当前字符串的引用。
2 如果常量池中不存在当前字符串:
若heap区存在该字符串的对象,则将该对象移至常量池,且返回该对象的引用。
若heap区也不存在该对象,则在常量池创建该字符串,并返回该字符串的引用。
(String s="1"等价于String s=“1”.intern())
常量池在堆区里边,但为了便于描述,以下的堆区都是指堆区除去常量池区以后的地方。
创建了1或2个对象。(取决于字符串常量池中原先是否存在abc字符串)
1 如果字符串常量池已存在abc字符串,则创建一个对象,在堆区。
2 如果字符串常量池不存在abc字符串,则创建两个对象,一个在常量池,一个在堆区。
jdk6和jdk区的区别:常量区从堆区外移至堆区内。
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
}
jdk6 下false false
jdk7 下false true
String s3 = new String(“1”) + new String(“1”);
生成了2个最终对象,分别是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的对象。中间还有2个匿名的new String(“1”)不去讨论。
此时s3引用对象内容是”11”,常量池中没有 “11”。
s3.intern();将 s3中的“11”字符串放入 String 常量池中。
jdk6: 在perm区字符串常量池中生成一个 “11” 的对象。
jdk7: 常量池不在 Perm 区域了而在heap区,不需要再创建一份对象了,而是可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的。
最后String s4 = “11”; 这句代码中”11”是显示声明的,会直接去常量池中创建,发现已经存在这一字符串,也就是指向 s3 引用对象的一个引用。所以 s4 引用就指向和 s3 一样了。因此最后的比较 s3 == s4 是 true。
String s = new String(“1”);生成了2个对象。常量池中的“1” 和 JAVA Heap 中的字符串对象。
s.intern(); 去常量池中寻找后发现 “1” 已经在常量池里了,不做改变。
String s2 = “1”; 生成一个 s2的引用指向常量池中的“1”对象。
而s指向JAVA Heap 中的字符串对象,不指向常量池,故二者不相等。
public static void main(String[] args) {
String s = new String("1");
String s2 = "1";
s.intern();
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);
}
jdk6 下false false
jdk7 下false false
String s3 = new String(“1”) + new String(“1”);生成了2个最终对象,分别是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的对象。
String s4 = “11”;常量池中不存在“11”对象,在字符串常量池创建“11”的字符串。s4指向该处。
s3.intern();,常量池中“11”对象已经存在了,因此不发生变化。s3仍是原heap区的一个对象(不在常量池)。s3与s4不同。
String s = new String(“1”);的时候已经在常量池区生成“1”对象了。
s.intern();不做变化。
s2声明都是直接从常量池中取地址引用。
s指向heap区(常量池以外)的对象“1”。
s 和 s2 的引用地址不相等。
参考链接1
参考链接2
参开链接3