关于StringTable和intern()的知识点

整理于

美团技术团队
尚硅谷的JVM课程

  • 声明为final,不可继承

  • 实现了Serializable接口:表示字符串可以支持序列号

    Comparable接口 表示可以比较大小

  • 在JDK1.8中使用的是char数组,但是在JDK1.9中使用的是byte的数组,因为通过研究表明,大部分对空间大多数是拉丁字符,所以说一个字节即可解决问题,除此之外,为了解决解决中文的问题,改成了btye[ ] 加上了编码的标识,节约了一些空间

  • 基于以上的String buffer等类也做出了对应的修改

不可变性

不可变的序列,当数组做出修改后,不可变,

  • 当对String重新赋值之后,将会重写执行内存的区域赋值,而不使用原有的
  • 当进行拼接,替换等等之后都会重写赋值

在String不可变性,所以说在传递中如果传递的话,不会修改原本的数值,而是会返回一个新的。

字符串常量池

  • 字符串常量池中不会存储相同的字符串,底层是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。
  • 可以通过去设置JVM的参数来进行设置Hashtable来控制大小。
  • 在jdk6中StringTable默认大小是1009的长度,在jdk7中是60013的长度。
  • jdk8中如果要设置最小值,则最小的设置大小是1009.

String的内存分配

  • 在Java语言当中,为8种基本类型和String都提供了常量池的概念,为了让他们可以运行的更快、更加的节省内存。
  • String的常量池比较特殊,存在两种
    • 直接使用双引号声明出来的String对象会直接存储在常量池中。
    • 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中
  • 常量池的存放的位置
    • jdk6 字符串常量池放在永久代
    • jdk7 中,Orcale做了很大的改动放在了堆中,即将字符串的字符串的常量池都存放在了Java的堆中
      • 所以所有的字符串都保存在了堆中,和其他的对象一样,这样在调优的时候调整堆的大小就行了。
    • jdk8 中字符串在堆中的字符串常量池

image-20210908180156968

image-20210908180240091

为什么要调整位置

  1. permSize 默认比较小
  2. 永久代垃圾回收频率低

字符串拼接操作

  1. 常量和常量的拼接都在常量池中,编译期优化

  2. 常量池中不会存在相同的内容的常量

  3. 只要有一个是变量则结果在堆中,原理是StringBuilder

  4. 拼接结果是调用intern()方法,讲常量池中没有字符串的常量放进去,返回地址

image-20210731174140713

  • 当发生变量拼接的时候,会执行
      1. StringBuilder s = new StringBuilder ();
      2. s.append(“a”);
      3. s.append(“b”);
      4. s.toString() --> 约等于 new String(“ab”)
  • 当时常量的时候,则会直接进行拼接,在编译器优化

注意:

  1. 在进行字符串拼接的时候,尽量使用StringBuilder来进行操作,这样会提高效率,而是还用String的字符串来进行的时候实际上是多次创建StringBuilder
  2. 在使用String时,内存占用了较多的StringBuilder和String,内存会占用较多的空间,GC也需要时间
  3. 在使用StringBuilder的时候,如果确定大小的话,最好创建的时候,直接创建一个带长度 构造,这样就可以减少扩容的次数

intern()方法

当方法被调用的时候,发现当前对象在字符串常量池中存在一个对象时,则返回字符串常量池中的对象的地址,反之,如果字符串常量池中没有的话,则会添加到字符串常量池,随后返回该添加的地址。

如果保证s指向的是字符串常量中的数据呢

  1. String s=“sadsadsad”;
  2. String s=new String(“aa”).intern();

重点

第一段代码

public class A {
    public static void main(String[] args) {
        String s = new String("1");
        s.intern();
        String s2 = "1";
        System.out.println(s == s2);   // jdk 6 false  jdk 7 false

        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = "11";
        System.out.println(s3 == s4); // jdk 6 false  jdk 7 true
    }
}

jdk6图

jdk7图1

注:图中绿色线条代表 string 对象的内容指向。 黑色线条代表地址指向。

JDK 6 解释

  • 在JDK 6 当中,字符串的常量都是保存在了Perm区,Perm区和正常的Java Heap区是分开的,所以说生产的对象的常量必定是存在于Java方法区,然后new 出来的是存在于堆区。使用 intern 后。
    • 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
    • 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址

JDK 7 解释

  • 在JDK7,当中,常量池将不再存储对象,而是直接存储堆中的引用,这份引用执行的可以是堆中的地址

第二段代码

public class A {
    public static void main(String[] args) {
        String s = new String("1");
        String s2 = "1";
        s.intern();
        System.out.println(s == s2);  // jdk 6  false  // jdk7 + false

        String s3 = new String("1") + new String("1");
        String s4 = "11";
        s3.intern();
        System.out.println(s3 == s4); // jdk 6  false  // jdk7 + false
    }
}

jdk7图2

解释:

  • 在JDK 6 中是一致的,于是不再重复
  • 在JDK 7 + 中,假如说常量池中,已经存在了对象,则不会进行修改。

总结string的intern ()的使用:

jdk1.6中,将这个字符串对象尝试放入串池。

  • 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
  • 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址

Jdk1.7起,将这个字符串对象尝试放入串池。

  • 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
  • 如果没有,则会把对象的引用地址复制一份,放入串池,并返回串池中的引用地址,

面试题

  1. new String(“ab”) 会创造几个对象

2个

如何证明?

字节码

  1. String str =new String(“a”)+new String(“b”)有几个对象

对象1:new StringBuilder()

对象2:new String (“a”)

对象3:“a”

对象4: new String(“b”)

对象5:“b”

深入剖析:

StringBuilder 的 toString()中

对象6:new String(“ab”)

//在字符串常量池中,没有生成ab

intern()的效率

当使用intern之后可以有效的减少很多的空间。当存在当量的重复的字符串的时候

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值