JVM 补充——StringTable

本文详细讨论了Java中的String类型,包括其基本特性(如不可变性、序列化和大小比较)、内存分配机制(常量池与String.intern()),以及在Java7和8中字符串池的变化。特别强调了StringBuilder在拼接操作中的优势和intern()方法的使用策略。
摘要由CSDN通过智能技术生成

具体哪些String是相等的,各种String的情况,看这个:

https://javaguide.cn/java/basis/java-basic-questions-02.html#string-%E4%B8%BA%E4%BB%80%E4%B9%88%E6%98%AF%E4%B8%8D%E5%8F%AF%E5%8F%98%E7%9A%84

String的基本特性

  • String:字符串,使用一对“”引起来表示
    • 声明方式和基础类型类似:String str = “abc”;
    • 也可以:String str = new String(“hello”);
  • String声明为final的,不可被继承
  • String实现了Serializable接口:表示字符串是支持序列化的
    • 实现了Comparable接口:表示String可以比较大小
  • String 在Java8时,内部使用final char[] value来存储字符串数据。Java9改成了byte[]加编码标记哪种正在使用哪种字符编码,节省了一些空间
    • 原因是设计者们发现String里面大部分是拉丁字符,而拉丁字符只占一个字节,浪费了一半的空间
    • 和String有关的类,比如StringBuilder,StringBuffer都做出了相应的改变
  • String的不可变性
    • 当对字符串重新赋值时,会新建一个字面量,然后把新的字面量的地址赋值给String变量
    • 当对现有的字符串进行连接操作时,也是创建新对象,,,
    • 当调用String的replace方法修改指定字符或字符串时,也是返回一个新对象,,,
  • 字符串常量
    • 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
    • 字符串常量池是不会存储相同内存的字符串的【Java语言规范里要求完全相同的字符串字面量应该包含同样的Unicode字符序列(包含同一份码点序列的常量),并且必须是指向同一个String类实例】
    • 字符串常量池是一个固定大小的HashTable(数组+链表),默认大小长度为1009。如果放进常量池的String非常多,就会造成Hash冲突严重,导致链表会很长,链表长了之后调用String.intern时的性能会大幅下降
    • -XX:StringTableSize 可设置StringTable的长度
    • jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTableSize设置没有要求
    • jdk7中StringTable的长度默认值是60013
    • jdk8中默认值一样,但是设置StringTable的长度时,1009是可设置的最小值

String的内存分配

  • 在Java语言中有8种基本数据类型和一种比较特殊的类型String。这些类型为了使它们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念
  • 常量池就类似一个Java系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的。String的常量池比较特殊。它的主要使用方法有两种
    • 直接使用双引号声明出来的String对象会直接储存在常量池中
      • 比如 String info = “atguigu.com”;
    • 如果不是用双引号声明的String对象,可以使用String提供的intern()方法
  • 变化
    • Java 6及以前,字符串常量池存放在永久代
    • Java 7中Oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆中
      • 所有的字符串都保存在堆中,和其他普通对象一样,这样可以让你在进行调优应用使仅需要调整堆大小就可以了
      • 字符串常量池原先使用得比较少(因为怕永久代的OOM),现在常量池放进堆里面,就可以重新考虑在Java 7中使用String.intern
    • Java 8元空间,字符串常量仍然在堆中
    • 为什么字符串常量池要移入堆中?
      • 1.因为永久代比较小,字符串一多就容易OOM
      • 2.永久代很少GC,大量的空间被占用,不能及时清理

字符串拼接操作

  • 1.常量与常量的拼接结果在常量池,原理是编译期优化
  • 2.常量池中不会存在相同内容的常量
  • 3.只要其中有一个是变量【注意是变量,加了final就是常量引用,是常量相加,不是变量相��】,则相当于在堆中new String() (新建了一个对象),它的值为拼接的结果。变量的拼接原理是StringBuilder
    • 所以,类,方法,变量等,能用final修饰的尽量用
  • 4.如果拼接的结果调用intern()方法,如果常量池没有和这个拼接结果一样的字符串,常量池就新建个对象,并返回此对象地址。如果有了就直接返回那个字符串的地址
    • 拼接之后不调用 intern()方法 常量池是没有这个对象的
  • 请添加图片描述
  • 请添加图片描述
  • 使用StringBuilder拼接字符串比直接拼接字符串好
    • 1.使用StringBuilder拼接比直接拼接快很多倍。直接拼接,会创建过多的StringBuilder和String对象。而使用StringBuilder拼接,自始至终只产生一个StringBuilder对象
    • 2.直接拼接,创建过多对象,给GC带来压力
    • 改进:如果确定拼接之后的字符串长度不会超过某个最大值,可以用StringBuilder构造器指定大小,防止StringBuilder频繁扩容
      • StringBuilder s = new StringBuilder(high level);

intern()的使用

  • 如果不是用双引号声明的String对象,可以使用String提供的intern方法:intern方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。
    • 比如:String myInfo = new String(“I love atguigu”).intern();
  • 也就是说,如果在任意字符串上调用String.intern方法,其返回结果指向的那个类实例,必须和直接以常量形式出现的字符串实例完全相同。因此,下列表达式的值必定为true:
    • (“a”+“b”+“c”).intern() == “abc”
  • 通俗点讲,Interned String 就是确保字符串在内存中只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池(String Intern Pool)

请添加图片描述

  • new String(“a”) 会产生几个对象?2个

    • 一个是new 的 String对象
    • 一个是在常量池产生的常量 “a”(前提是常量池之前没有"a")
  • new String(“a”) + new String(“b”)会产生几个对象? 6个

    • 一个是StringBuilder因为是变量和变量相加,不是常量+常量
    • 一个是 new 的String
    • 一个是在常量池产生的常量"a"
    • 一个是new 的String
    • 一个是在常量池产生的常量"b"
    • 还有一个!StringBuilder最后还会调用toString()方法,toString方法会new 一个String()对象。
      • 但是!!!在字节码文件看toString的指令,里面没有在常量池创建常量,就是这里的代码,常量池不会多一个"ab"
  • 一个很难的面试题

    • Java7中,将常量池放入了堆中。为了节省空间,它在常量池里不是新建一个常量"11",而是新建一个指针 指向"11",所以s3和s4一样
      • 而在其他情况下,一般都是直接赋值 “”比如 String s = “11”,或者 String s = new String(“11”) 这两个过程都是会在常量池里生成常量的
      • String s3 = new String(“1”) +new String(“1”) 这种生成了String对象,但是没有在常量池里生成常量的情况,真的很特殊
      • **也只有在这种情况下使用 s3.intern()方法会使用 现有String的对象,而不是自己创建一个【其他情况调用****intern()**还是会直接新建常量的】
    • 请添加图片描述
    • 拓展
    • 请添加图片描述
    • 总结String的intern()方法的使用:
      • jdk1.6中,将这个字符串对象尝试放入常量池
        • 如果常量池有,则不会放入。返回已有的常量池中的对象的地址
        • 如果没有,就会将此对象复制一份(就是新建一个值一样的对象),放入常量池,并返回常量池中的对象地址
      • jdk1.7中,将这个字符串对象尝试放入常量池
        • 如果常量池有,则不会放入。返回已有的常量池中的对象的地址
        • 如果没有,则会把对象的引用地址复制一份,放入常量池,并返回常量池中的引用地址
    • 当有大量字符串循环赋值的时候,使用intern()可以大大节省空间和加快速度
      • 请添加图片描述
        请添加图片描述
        G1对堆中String对象的去重(常量池都是唯一的,不用去重)
  • 请添加图片描述

  • 请添加图片描述

  • 感觉应该挺好用,不知道为什么不默认开启

  • 请添加图片描述

  • 28
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沛权

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值