问题的出现
String对象我们写Java的不可能没用过吧,但是你知道字符串常量池吗?今天就来聊聊JVM底层的字符串常量池,什么时候把字符串放进常量池?JDK1.6跟JDK1.7的常量池有什么变化?除了字符串常量池还有没有其他的常量池
先来看一段代码1
2
3
4
5
6
7
8
9
10String s1 = new String("1");
s1.intern();
String s2 = "1";
System.out.println(s1==s2); // false
String s3 = new String("2") + new String("2");
s3.intern();
String s4 = "22";
System.out.println(s3 == s4); // true
总共有两句输出,你知道两句输出的结果是什么吗?虽然注释第一个为false,第二个为true。但是在JDK1.6里面,两个都为false,你知道为什么吗?请开始我的表演。
字符串常量池
简介
说白了,字符串常量池就是用来存放字符串数据(在内存中)。但是注意,字符串常量池只是JVM中的一种常量池,其他的还有 class文件常量池 , 运行时常量池
一时间也说不清楚其他两种常量池的作用和关系,只知道运行时常量池包含class文件常量池和字符串常量池,而且运行时常量池都存在于 方法区
在JDK1.6及以前版本,字符串常量池是存在于运行时常量池,也就是存在于方法区中的。但是,JDK1.7以后,字符串常量池就被移出到 堆 里面了。
什么时候才可以向字符串常量池添加数据
- 1、常量的字符串,就是被关键字final修饰的字符串,会在编译时添加进字符串常量池
- 2、显示声明的时候。比如String s=”1”,这时,当执行这段代码的时候,就会向字符串常量池里面添加数据,叫做字面量
- 3、调用String类的intern方法时(这个方法在JDK1.6和1.7里面有差异,等下面再说)
解析
经过上面的一番操作,我们应该理解常量池(在方法区中)和堆是两个不同的东西。所以,我们可以推测,上面的代码之所以输出的结果会不一样,原因应该是有些对象是在堆里面,有些对象是常量池里面的。
首先看第一个输出那段代码
String s1 = new String(“1”);请问这句代码一共创建了多少个对象?1个?错!是两个。s1是一个,还有一个就是上面说过的字面量,”1”就是字面量,此时会在字符串常量池里面创建一个”1”的对象,所以总共两个。 s1指向的是堆里面的”1”对象 ,接下来调用intern方法,下面就来解释下该方法的作用
intern()方法的作用
简单的讲,intern方法就是 向常量池里面添加数据 ,前提是 该数据还不在常量池里面 ,然后返回一个String对象
JDK1.6里面,intern方法向字符串常量池里面添加的是对象,也就是比如s1.intern(s1是String对象,值为1),如果常量池里面还没有”1”这个对象,就会将”1”这个对象添加到常量池里面。如果常量池里面已经有”1”这个对象了,则返回 常量池里面的对象 。
JDK1.7里面,如果常量池里面有”1”这个对象,做法跟1.6时一样,返回 常量池里面的对象 ,但是,如果常量池里面没有该值的对象,则添加的是 对s1在堆中的对象的引用 (说得有点绕口,说白了就是添加在堆中的对象)。
我们看下代码就知道了
1
2
3
4
5
6String s1 = new String("1");
String s2 = s1.intern();
System.out.println(s1 == s2); // false
String s3 = "1";
System.out.println(s2 == s3); // true
执行s1.intern返回的是常量池里面的对象,因为上一句代码已经创建了两个对象,一个在堆里面,一个在常量池里面。所以intern方法会发现常量池里面已经有”1”这个对象了,则返回该对象。s3也是常量池里面的”1”对象,所以一个输出false,一个输出true.
继续分析
我们前面说到intern方法只是检查常量池里面有没有该值,有则返回(返回的是常量池里面的对象),没有则添加(JDK1.6和1.7的添加规则不同)。接下来String s2=”1”;因为s2是字面量,也就是显示声明,而且常量池里面已经有”1”这个对象了。所以答案就很明显了,s1是在堆里面的”1”对象,s2是在字符串常量池里面的”1”对象,所以两者是不会相等的,结果为false。
继续分析第二段代码
String s3 = new String(“2”) + new String(“2”);这里虽然也是创建了两个对象,一个是”22”的堆对象,一个是”2”的常量池对象,注意:没有”22”常量池对象。
接下来调用intern方法,因为字符串常量池还没有”22”这个对象,所以重点来了,如果是JDK1.7,添加的是 堆里面”22”这个对象的引用(说白了就是堆里面”22”这个对象) ,所以常量池里面的”22”这个对象会指向堆里面”22”这个对象。所以下一句代码String s4=”22”,貌似是常量池里面的”22”对象,但是因为此时常量池的”22”对象已经指向了堆里面的”22”对象,所以说s4也是堆里面的对象。所以输出结果为true。
但是如果是JDK1.6,在调用intern方法的时候,因为常量池的”22”这个对象还不存在,所以就添加了”22”这个常量池对象,注意: 是常量池对象 ,所以下一句的String s4=”22”就是常量池对象了,结果就输出了false。
总结
说了那么多,其实就一句——常量池对象跟堆里面的对象时不一样的(只是简单理解,不要当真)
贴出总代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71package com.lnw.stringpool;
public class Test {
public static void main(String[] args) {
/**
* 第一条代码一运行,会创建两个对象。
* 一个对象是常量池 "1"的对象,存储在字符串常量池中
* 另一个对象是 String 对象s1
* 这里s1因为是new出来的,所以s1是堆里面的String对象
*/
String s1 = new String("1");
/**
* 调用intern方法,如果"1"不在字符串常量池中,则将s1对象的引用(内存中的地址)添加到字符串常量池中
* JDK1.7以上就是添加对象的引用,但是1.7以下是直接将"1"添加到常量池
* 但是如果存在这个"1"值的对象,则返回该对象
* 返回的常量池里面的对象
*/
String s5 = s1.intern();
/**
* 因为s5是返回常量池里面的"1"对象
* s1是堆里面的对象
*/
System.out.println(s1 == s5); // false
/**
* 第三条代码运行时,因为上面第一条代码已经将"1"放进了字符串常量池了
* 所以s2是字符创常量池里面的"1"对象
*/
String s2 = "1";
/**
* 因为s1是堆里面String对象,s2是字符串常量池"1"的对象,所以为false
*/
System.out.println(s1==s2); // false
/**
* 因为s5是返回常量池里面的"1"对象
* s2也是常量池里面"1"的对象
*/
System.out.println(s5 == s2); // true
/**
* 这里的s3显然是"22",但是,"22"还没有进入字符串常量
* 也就是说,目前字符串常量里面只有"2"这个对象
* 但是,堆中有"22"这个对象
* s3就是指向"22"这个堆中的对象的引用
*/
String s3 = new String("2") + new String("2");
/**
* 因为"22"还没有进入字符串常量,所以把s3这个对象对应的在堆中"22"这个对象的引用推入字符串常量池
* 也就是说字符串常量池的"22"会指向堆里面"22"这个对象
* 如果值已经存在,则返回常量池里面值"22"的对象
*/
String s6 = s3.intern();
/**
* 因为s6和s3都是堆里面"22"这个对象的引用,所以为true
*/
System.out.println(s6 == s3); // true
/**
* 因为上面的intern方法已经将"22"这个对象的引用放进字符创常量池
* 所以s4指向的堆里面"22"这个对象
*/
String s4 = "22";
/**
* 因为s4和s3引用的都是堆里面的对象,所以为true
*/
System.out.println(s3 == s4); // true
System.out.println(s6 == s4); // true
}
}