Java 字符串常量池

目录

一、池化概念

二、字符串常量池

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中引入了:

  1. class 文件常量池:每个 Java 源文件编译后生成的" .class "文件中会保存当前类中的字面常量以及符号信息
  2. 运行时常量池:在 .class 文件被加载时,.class 文件中的常量池被加载到内存中,称为运行时常量池,运行时常量池每个类都有一份
  3. 字符串常量池

二、字符串常量池

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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值