目录
🐒 什么是"池"
- "池" 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各种 "内存池", "线程池", "数据库连接池" ....
- 比如电脑的硬盘,你有256G,有一天你下了一个3A游戏,结果发现空间不够用了,这时候你就想着买一个硬盘装上去,中间你要等快递,要装盘。
- 而池的方式就是直接给硬盘上到最大容量,一条不够装十条🤬,你要下游戏尽管用,随下随用,效率非常高。
- 常量池又分为 Class 文件常量池、运行时常量池、字符串常量池。
🐒 字符串常量池
public static void main(String[] args) {
String a = "hello";
String b = "hello";
System.out.println(a == b);
String aa = new String("haha");
String bb = new String("haha");
System.out.println(aa == bb);
}
先看下上面的代码,想一下输出结果分别是什么?
看到输出结果后为啥 new 的 String 字符串为啥比较结果是 false 呢?
这一切都要归于字符串常量池。
🍂 字符串常量池位置的变化
- 在 jdk1.7 之前,字符串常量池是在方法区的运行时常量池当中,此时方法区的实现为永久代(PermGen Space )。
- 在 jdk1.7中,字符串常量池从方法区中拿出到了堆 Heap 中。
- 在 jdk1.8中,Hotspot 虚拟机废除了永久代,取而代之的是 Metaspace (元空间),字符串常量池还在堆中。
永久代(PermGen Space ):是指内存的永久保存区域,他是一个固定大小的区域,默认为:1009,可以通过 -XX:MaxPermSize=N 来设置永久代的空间大小, 但是他还是固定的。
方法区其实也是堆的一部分,为了区分又叫非堆,即永久代也是堆的一块区域。
Metaspace (元空间) :元空间和永久代的区别是不在虚拟机中,而是在本地内存中。
🍂 String 在字符常量池中的创建
字符串常量池在JVM中是StringTable类,实际是一个固定大小的 HashTable(哈希表),哈希表的底层又是数组 + 链表 + 红黑树的结构。
- 当我们创建 a 和 b 对象时,首先会 JVM 会在字符串常量池中查找 "hello" 是否存在,如果存在就会返回池中对象的引用,如果不存在,则先创建一个对象,然后将对象的引用放在字符串常量池中。
- 哈希表的每一个索引处都是一个结点,有key(哈希值)、value(String 对象引用)、next,而 value 指向了String 对象。
- String 对象有俩个值,分别是 value 和 hash(默认是0) 。
- value 指向的就是我们的char 数组。
- aa、bb 因为时 new 的新对象,创建了俩个新的对象,首先根据 aa new String() 往构造函数内传的参数 "bbb" 入字符串常量池(前提是池内没有此字符串),然后创建新对象指向我们的池内的对象指向的数组。
1.思考下方代码一共创建了几个对象
public static void main(String[] args) {
String a = "hello";
String b = "hello" + "World";
String c = "helloWorld";
String d = new String("haha");
}
2.思考下方代码 a == b 吗
public static void main(String[] args) {
char[] ch = new char[]{'h','e','l','l','o'};
String a = new String(ch);
String b = "hello";
System.out.println(a == b);
}
1.答案是5 个对象。
为什么是5 个对象呢?
- 首先 a 创建了一个String 对象("hello"),然后放入到池中。
- b 创建了二个 String 对象,然后放入池中,分别是 "World" 和 "helloWorld"。
- c 没有创建对象,因为在池中已经找到了 "helloWorld" 的对象,此时返回此对象的引用给 c。
- d 创建了俩个对象,分别是 new 的对象 b,以及 "haha",因为在传参 String 构造函数时在池中未找到 "haha",此时创建新对象放入池中,然后创建对象 b 指向 (指向 "haha" 对象的引用)。
2.答案是false。
为什么是false呢?
首先我们创建了 ch 字符数组,然后传参给 a,此时 a 是new 的对象,并不会放入池中, ch 为字符数组,传入 String 构造函数中,我们看一下 构造函数的注解:
分配一个新字符串,使其表示字符数组参数中当前包含的字符序列。字符数组的内容被复制;字符数组的后续修改不会影响新创建的字符串。
参数:
value-字符串的初始值
也就是说,我们传入的字符数组在构造函数中会复制成一份新的字符串 ,然后我们的对象 a 的value指向这个字符串,注意这个a并不会 入到池中。
此时我们创建字符串 "hello",发现池中并没有,然后创建新对象进行入池,返回这个对象的引用给 b 这一系列的操作。
接下来我们使用一个方法即可使 a == b。
🍂 intern 方法
intern 是可以将字符串手动入池的一个方法,来看下源码
第一行:返回字符串对象的规范表示。
第二行:最初为空的字符串池由String类私下维护(也就是字符串常量池,jdk1.8在堆中)。
第三行:调用intern方法时,如果池中已经包含由equals(Object)方法确定的等于此String对象的字符串,则返回池中的字符串。否则,将此String对象添加到池中并返回对该String对象的引用。
你可能注意到了 intern 方法是由 native 修饰的, native 的意思是指底层由 c++ 代码实现的,我们点到方法下是看不到源码的。
当我们对 a 进行入池时,String b = "hello" 时在池中找到,返回引用,即 a == b,输出 true。
public static void main(String[] args) {
char[] ch = new char[]{'h','e','l','l','o'};
String a = new String(ch);
a.intern();
String b = "hello";
System.out.println(a == b);
}
本篇文章到这里就结束了,如果觉得可以的话给个三连支持一下吧。