Java 字符串常量池

目录

🐒 什么是"池"

🐒 字符串常量池 

🍂 字符串常量池位置的变化

🍂 String 在字符常量池中的创建

🍂 intern 方法


🐒 什么是"池"

  • "池" 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各种 "内存池", "线程池", "数据库连接池" ....
  1. 比如电脑的硬盘,你有256G,有一天你下了一个3A游戏,结果发现空间不够用了,这时候你就想着买一个硬盘装上去,中间你要等快递,要装盘。
  2. 而池的方式就是直接给硬盘上到最大容量,一条不够装十条🤬,你要下游戏尽管用,随下随用,效率非常高。
  • 常量池又分为 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 个对象呢?

  1. 首先 a 创建了一个String 对象("hello"),然后放入到池中
  2. b 创建了二个 String 对象,然后放入池中,分别是 "World" 和 "helloWorld"。
  3. c 没有创建对象,因为在池中已经找到了 "helloWorld" 的对象,此时返回此对象的引用给 c。
  4. 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);
    }

 

本篇文章到这里就结束了,如果觉得可以的话给个三连支持一下吧。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值