目录
一、池化概念
二、字符串常量池
1. 概述
2. String对象的创建过程
1)直接使用字符串常量进行赋值
2)通过new创建String类对象
3)结论
4)intern方法
一、池化概念
先看如下的一段代码:
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s3 == s4); // false
之所以会出现上述情况,是因为在Java程序中,类似于:1,2,3,3.14,"hello"等字面类型的常量经常频繁使用,为了使程序的运行速度更快,更节省内存,Java为8种基本数据类型和String类都提供了常量池。其中为String类提供的常量池叫做字符串常量池。
"池" 是编程中的一种常见的,重要的提升效率的方式,除了常量池,还有各种内存池,线程池,数据库连接池等。
为了节省存储空间以及程序的运行效率,Java中引入了:
- class 文件常量池:每个 Java 源文件编译后生成的" .class "文件中会保存当前类中的字面常量以及符号信息
- 运行时常量池:在 .class 文件被加载时,.class 文件中的常量池被加载到内存中,称为运行时常量池,运行时常量池每个类都有一份
- 字符串常量池
二、字符串常量池
1. 概述
字符串常量池在 JVM 中是 StringTable 类(实际是一个固定大小的 HashTable )
不同 JDK 版本下字符串常量池的位置以及默认大小是不同的:
JDK 版本 | 字符串常量池位置 | 大小设置 |
Java 6 | (方法区)永久代 | 固定大小:1009 |
Java 7 | 堆中 | 可设置,没有大小限制,默认大小:60013 |
Java 8 | 堆中 | 可设置,存在大小限制,最小:1009 |
2. String 对象的创建过程
不同 JDK 版本对于字符串常量池的处理方式不同,以下是 Java 8 中的情况:
1)直接使用字符串常量进行赋值
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true
上述在常量池中的情况大致如下:
StringTable 底层是一个哈希表,因此是通过数组+链表来组织数据的,数组上的每个元素是一个链表:
s1 创建时,常量池中没有 hello 对应的String对象,因此需要创建一个添加到常量池中(创建一个节点,插入到链表中)。
节点的 String 对象引用就指向了一个新的 String 对象,该对象的字符数组引用就指向了一个内容为 hello 的新的字符数组。然后将该 String 对象引用赋值给 s1。
s2 创建时,发现常量池已经有了 hello 对应的 String 对象,因此就直接将常量池的 String 对象引用赋值给 s2。
因此 s1 和 s2 是指向了同一个 String 对象的引用。
2)通过new创建String类对象
s3 创建时,堆中会创建一个全新的 String 对象,然后也会去常量池寻找是否存在字面值相同的String 对象,此时发现有(但是不会直接将常量池中的 String 对象的引用直接赋值给 s3 ),然后这个全新的 String 对象的 val 指向常量池中的 String 对象的 val 指向的字符数组,相当于这个全新的对象和常量池中的对象内部的 val 引用指向的是同一个字符数组。
如果没有,那么就还是会创建一个对应的 String 对象添加到常量池,大致过程和上述一致。并且添加到常量池的对象和上述创建的全新的对象,毫无关系,是两个不同的对象,只是它们的 val 引用指向的是同一个字符数组。
s4 同理。
因此 s3 和 s4 都是全新的 String 对象,和其他 String 对象不相同。
3)结论
无论如何,所有方式创建的字符串,常量池中都会保存一份。使用第一种方式创建 String 对象的效率更高,并且更节省空间。因为第一种方式没有创建出新的 String 对象,都是指向常量池中的 String 对象的引用。而第二种方式每次都是创建一个全新的 String 对象。
4)intern方法
intern 方法的作用就是手动将创建好的 String 对象引用添加到常量池当中
char[] ch = new char[]{'a', 'b', 'c'};
String s1 = new String(ch);
String s2 = "abc";
System.out.println(s1 == s2); // false
char[] ch = new char[]{'a', 'b', 'c'};
String s1 = new String(ch); // s1 对象的引用并不在常量池中
s1.intern(); // 将 s1 对象的引用添加到常量池中
String s2 = "abc"; // ”abc“ 对象的引用已经存在于常量池当中,直接取引用进行赋值
System.out.println(s1 == s2); // true
第一种情况示意图:
第二种情况的示意图:
注意:String类传入一个字符数组的构造方法,内部不是直接使用传入的字符数组,而是拷贝一个新的。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}