乐趣区

关于后端:正确理解和使用JAVA中的字符串常量池

前言
钻研表明,Java 堆中对象占据最大比重的就是字符串对象,所以弄清楚字符串常识很重要,本文次要重点聊聊字符串常量池。Java 中的字符串常量池是 Java 堆中的一块非凡存储区域,用于存储字符串。它的实现是为了进步字符串操作的性能并节俭内存。它也被称为 String Intern Pool 或 String Constant Pool。那让我来看看到底是怎么一回事吧。
了解字符串常量池
当您从在类中写一个字符串字面量时,JVM 将首先查看该字符串是否已存在于字符串常量池中,如果存在,JVM 将返回对现有字符串对象的援用,而不是创立新对象。咱们通过一个例子更好的来了解。
比方上面的代码:
String s1 = “Harry Potter”;
String s2 = “The Lord of the Rings”;
String s3 = “Harry Potter”;
复制代码
在这段代码中,JVM 将创立一个值为“Harry Potter”的字符串对象,并将其存储在字符串常量池中。s1 和 s3 都将是对该单个字符串对象的援用。
如果 s2 的字符串内容“The Lord of the Rings”不存在于池中,则在字符串池中生成一个新的字符串对象。

两种创立字符串形式
在 Java 编程语言中有两种创立 String 的办法。第一种形式是应用 String Literal 字符串字面量的形式,另一种形式是应用 new 关键字。他们创立的字符串对象是都在常量池中吗?

字符串字面量的形式创立

String s1 = “Harry Potter”;
String s2 = “The Lord of the Rings”;
String s3 = “Harry Potter”;
复制代码

new 关键字创立

String s4 = new String(“Harry Potter”);
String s5 = new String(“The Lord of the Rings”);
复制代码
咱们来比拟下他们援用的是否是同一个对象:
s1==s3 // 真
s1==s4 // 假
s2==s5 // 假
复制代码
应用 == 运算符比拟两个对象时,它会比拟内存中的地址。

正如您在下面的图片和示例中看到的,每当咱们应用 new 运算符创立字符串时,它都会在 Java 堆中创立一个新的字符串对象,并且不会查看该对象是否在字符串常量池中。
那么我当初有个问题,如果是字符串拼接的状况,又是怎么样的呢?
字符串拼接形式
后面讲清楚了通过间接用字面量的形式,也就是引号的形式和用 new 关键字创立字符串,他们创立出的字符串对象在堆中存储在不同的中央,那么咱们当初来看看用 + 这个运算符拼接会怎么样。
例子 1
public static void test1() {

  // 都是常量,前端编译期会进行代码优化
  // 通过 idea 间接看对应的反编译的 class 文件,会显示 String s1 = "abc"; 阐明做了代码优化
  String s1 = "a" + "b" + "c";  
  String s2 = "abc"; 

  // true,有上述可知,s1 和 s2 实际上指向字符串常量池中的同一个值
  System.out.println(s1 == s2); 

}
复制代码

常量与常量的拼接后果在常量池,原理是编译期优化。

例子 2
public static void test5() {

String s1 = "javaEE";
String s2 = "hadoop";

String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";    
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;

System.out.println(s3 == s4); // true 编译期优化
System.out.println(s3 == s5); // false s1 是变量,不能编译期优化
System.out.println(s3 == s6); // false s2 是变量,不能编译期优化
System.out.println(s3 == s7); // false s1、s2 都是变量
System.out.println(s5 == s6); // false s5、s6 不同的对象实例
System.out.println(s5 == s7); // false s5、s7 不同的对象实例
System.out.println(s6 == s7); // false s6、s7 不同的对象实例

}
复制代码

只有其中有一个是变量,后果就在堆中, 变量拼接的底层原理其实是 StringBuilder。

例子 3:
public void test6(){

String s0 = "beijing";
String s1 = "bei";
String s2 = "jing";
String s3 = s1 + s2;
System.out.println(s0 == s3); // false s3 指向对象实例,s0 指向字符串常量池中的 "beijing"
String s7 = "shanxi";
final String s4 = "shan";
final String s5 = "xi";
String s6 = s4 + s5;
System.out.println(s6 == s7); // true s4 和 s5 是 final 润饰的,编译期就能确定 s6 的值了

}
复制代码

不应用 final 润饰,即为变量。如 s3 行的 s1 和 s2,会通过 new StringBuilder 进行拼接
应用 final 润饰,即为常量。会在编译器进行代码优化。

妙用 String.intern() 办法
后面提到 new 关键字创立进去的字符串对象以及某些和变量进行拼接不会在字符串常量池中,而是间接在堆中新建了一个对象。这样不大好,做不到复用,节约不了空间。那有什么好方法呢?intern()就派上用场了,这个十分有用。
intern()办法的作用能够了解为被动将常量池中还没有的字符串对象放入池中,并返回此对象地址。
String s6 = new String(“The Lord of the Rings”).intern();
复制代码

s2==s6 // 真
s2==s5 // 假

退出移动版