"池" 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各种 "内存池", "线程池", "数据库连接池" ....
比如:家里给大家打生活费的方式
1. 家里经济拮据,每月定时打生活费,有时可能会晚,最差情况下可能需要向家里张口要,速度慢
2. 家里有矿,一次性打一年的生活费放到银行卡中,自己随用随取,速度非常快
方式2,就是池化技术的一种示例,钱放在卡上,随用随取,效率非常高。常见的池化技术比如:数据库连接池、线程池等。
为了节省存储空间以及程序的运行效率,Java中引入了:
1. Class文件常量池:每个.Java源文件编译后生成.Class文件中会保存当前类中的字面常量以及符号信息
2. 运行时常量池:在.Class文件被加载时,.Class文件中的常量池被加载到内存中称为运行时常量池,运行时常量池每个类都有一份
3. 字符串常量池
一、关于创建对象的思考
public static void main(String[] args) {
String str1 = "abcd";
String str2 = "abcd";
String str3 = new String("abcd");
String str4 = new String("abcd");
System.out.println(str1 == str2); // true
System.out.println(str3 == str4); // false
System.out.println(str1 == str3); // false
}
上述程序创建方式类似,为什么s1和s2引用的是同一个对象,而s3和s4不是呢?
在Java程序中,类似于:1, 2, 3,3.14,“hello”等字面类型的常量经常频繁使用,为了使程序的运行速度更快、更节省内存,Java为8种基本数据类型和String类都提供了常量池。
首先, 字符串常量池底层是一个StringTable的哈希表.
字符串常量池是存放在堆当中的一块区域. 那么它有一个特点: 只要是双引号""引起来的, 就会有两步操作.
首先, 在字符串常量池中检查是否有双引号引起来的对象(比如"abcd"), 如果没有就把它放进去.
然后在第二次再要放一个"abcd"的时候, 就会去检查在常量池当中, 是否有"abcd", 如果有就不会再重复存储一次了.
也就是说, 在字符串常量池中只会去维护一个"abcd"对象, 所以只要"abcd"存过, 就不会再存了.
这样有一个好处, 节省了内存, 提高了效率
那么它是如何存进字符串常量池的? 我们先来画一个草图简单理解.
注: 以上图中所有的"地址"都是随意写出来的, 以便演示, 不是真实情况下的地址.
下图是正确的图示.
二、字符串常量池(StringTable)
字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable,不同JDK版本下字符串常量池的位置以及默认大小是不同的:
JDK版本 | 字符串常量池位置 | 大小设置 |
---|---|---|
Java6 | 方法区 | 固定大小:1009 |
Java7 | 堆中 | 固定大小:1009 |
Java8 | 堆中 | 可设置,有范围限制,最小是1009 |
三、再谈String对象创建
1. 直接使用字符串常量进行赋值
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true
}
2. 通过new创建String类对象
结论:只要是new的对象,都是唯一的。
通过上面例子可以看出:使用常量串创建String类型对象的效率更高,而且更节省空间。用户也可以将创建的字符串对象通过intern
方式添加进字符串常量池中。
3. intern方法
intern
是一个native方法(Native方法指:底层使用C++实现的,看不到其实现的源代码),该方法的作用是手动将创建的String对象添加到常量池中。
public static void main(String[] args) {
char[] ch = new char[]{'a', 'b', 'c'};
String s1 = new String(ch); // s1对象并不在常量池中
//s1.intern(); // s1.intern();调用之后,会将s1对象的引用放入到常量池中
String s2 = "abc"; // "abc" 在常量池中存在了,s2创建时直接用常量池中"abc"的引用
System.out.println(s1 == s2);
}
// 输出false
// 将上述intern方法打开之后,就会输出true
注意:在Java6 和 Java7、8中Intern的实现会有些许的差别。
面试题:请解释String类中两种对象实例化的区别
JDK1.8中
1.String str = "hello";
只会开辟一块堆内存空间,保存在字符串常量池中,然后str共享常量池中的String对象
2.String str = new String("hello");
会开辟两块堆内存空间,字符串"hello"保存在字符串常量池中,然后用常量池中的String对象给新开辟的String对象赋值。
3.String str = new String(new char[]{'h', 'e', 'l', 'l', 'o'});
现在堆上创建一个String对象,然后利用copyof将重新开辟数组空间,将参数字符串数组中内容拷贝到String对象中