从String和new String 看java常量池

常量池

静态常量池(class文件常量池)

即class文件中的常量池,类加载后被放到运行时常量池中。包含字面量,类、方法的信息。占用class文件大部分空间。
使用javap命令可查看。

运行时常量池

运行时常量池(Runtime Constant Pool),是方法区的一部分。Class文件中除了有类的版本、
字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),
用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
运行时常量池相对于class文件常量池的另外一个重要特性是具备动态性,
java语言并不要求常量一定只有编译期才能产生,也就是并非预置入class文件常量池的内容才能进入方法区运行时常量池,
运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。
—— 摘自《深入理解java虚拟机》 周志明

划重点:

  1. 存放什么: 编译期生成的东西。
  2. 什么时候存放:类加载后。
  3. 特殊情況:动态性,即系统运行时动态放入池中,例如String类的intern()方法。

字符串池

最初为空的字符串池由String类私有维护。

new String(“s”)

java doc:

初始化一个新的Sting对象,这个对象与参数相等。换句话说,创建的String是参数的一个copy。

解读:
参数”s” 会在编译期被确定将会放入常量池,在系统启动的时候放入常量池中。
当调用new String("s")的时候,将常量池中的“s”字符串copy到堆中。
此时,内存中一共有两个“s”对象。

String.intern()

java doc:

调用此方法时,如果字符串池中已经有一个equal此String对象的字符串(由String的equals方法判定),则返回字符串池中的字符串。
否则,将此String对象的引用添加到池中,并返回对此String对象的引用。
因此,对于任何两个字符串s和t,当且仅当s.equals(t)为true时,s.intern()== t.intern() 才为true。

换句话说,就是如果字符串池中有,就返回池中对象;如果没有,就放入池中,返回当前对象。

测试

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
/**
* java 内存常量池测试
*/
public class Test {

public static void main(String[] args) {
System.out.println("m0=========");
m0();
System.out.println("m1=========");
m1();
System.out.println("m2=========");
m2();
System.out.println("m3=========");
m3();
}

public static void m0() {
String s1 = "a";
String s2 = new String("a");
s2.intern();
System.out.println(s1 == s2);
String s3 = new String("b") + new String("b");
s3.intern();
String s4 = "bb";
System.out.println(s3 == s4);
}

public static void m1() {
String s1 = new String("a");
String s = s1.intern();
String s2 = "a";
System.out.println(s1 == s2);
System.out.println(s == s2);
String s3 = new String("b") + new String("b");
s3.intern();
String s4 = "bb";
System.out.println(s3 == s4);
}

public static void m2() {
String s1 = new String("b");
String s2 = "b";
String s = s1.intern();
System.out.println(s1 == s2);
System.out.println(s == s2);
String s3 = new String("b") + new String("b");
String s4 = "bb";
s3.intern();
System.out.println(s3 == s4);
}

public static void m3() {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 188;
Integer f = 188;
Long g = 3L;
System.out.println(c == d);
//包装类也只是在对象数值在-128~127才可以使用这些常量池。
System.out.println(e == f);
System.out.println(c == (a + b));
System.out.println(c.equals(a + b));
System.out.println(g == (a + b));
System.out.println(g.equals(a + b));
}
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
m0=========
false
true
m1=========
false
true
false
m2=========
false
true
false
m3=========
true
false
true
true
true
false

m0

  • new String(“a”)是将常量池中的a复制了一份到字符串池。调用intern()方法的时候“a”已经在字符串池中了,
    这个操作对s1和s2都没有影响,s1!=s2。
  • s3指向堆内存,调用intern()之后这个引用被放入字符串池中。声明s4的时候也是用的此引用,s3==s4。

m1

  • 由于调用intern()方法的时候“a”已经在字符串池中了,所以s1!=s2
  • s是intern()返回的字符串池中的“a”,所以s==s2
  • 同理调用s3.intern()的时候“bb”已经在m0的时候放入字符串池中了,所以s3!=s4

m2

  • 调用s1.intern()的时候“b”已经在字符串池了,s1!=s2
  • s是intern()返回的字符串池中的“b”,所以as==s2
  • 同理调用s3.intern()的时候“bb”已经在m0的时候放入字符串池中了,所以s3!=s4

m3

  • 都是用常量池c==d
  • 大于127不使用常量池 e!=f
  • 使用常量池
  • equals相等
  • 相等
  • equals判断类型不相同

参考:

https://tech.meituan.com/in_depth_understanding_string_intern.html
https://blog.csdn.net/wangtaomtk/article/details/52267548


从String和new String 看java常量池
https://www.wekri.com/jvm/new-string/
Author
Echo
Posted on
August 29, 2018
Licensed under