• 探究java的intern方法


    本文主要解释java的inten方法的作用和原理,同时会解释一下经常问的String面试题。

    首先先说一下结论,后面会实际操作,验证一下结论。intern方法在不同的Java版本中的实现是不一样的。Java6之前是一种实现,Java6之后也就是Java7和Java8是另外一种实现。

    先说一下intern方法的定义 在Java的String类中是这样定义的,是一个本地方法,其中源码由C实现

    public native String intern();

    再来看一下源码的注释描述:

    * <p>
    * When the intern method is invoked, if the pool already contains a
    * string equal to this {@code String} object as determined by
    * the {@link #equals(Object)} method, then the string from the pool is
    * returned. Otherwise, this {@code String} object is added to the
    * pool and a reference to this {@code String} object is returned.
    * <p>

    翻译过来的意思就是:如果常量池中已经有了此字符串,那么将常量池中该字符串的引用返回,如果没有,那么将该字符串对象添加到常量池中,并且将引用返回。

    首先要明白,这里注释的该字符串是调用此方法的字符串,返回的是引用。

    下面介绍一个new String的知识点,下文再讨论intern的实现的会用到。

    有个经典的Java基础面试题,new String("xyz")会创建几个对象?

    动手实践后,发现再new String("xyz")有可能会创建一个(不好验证,但是可以通过下文的分析得出结论),也有可能会创建两个(可验证)。

    结论:如果常量池中没有 xyz,那么就会创建两个,现在堆中创建一个,然后将对象copy到常量池中,也就是第二次创建,堆中和常量池中是两个对象。

     

    上图的版本是Java8版本。

    Java6版本:

    intern方法作用:确实如上述注释上所描述,如果常量池中没有字符串,则将该字符串对象加入常量池,并返回引用。

    ** 这里需要注意:Java6中常量池是在方法区中,而Java1.6版本hotspot采用永久带实现了方法区,永久代是和Java堆区分的,即就是常量池中没有字符串,那么将该字符串对象放入永久带的常量池中,并返回其引用。

     

    Java7和Java8版本:

    intern方法作用:和注释描述的并不同,

    如果常量池有,那么返回该字符串的引用。

            如果常量池没有,那么如果是”a“.intern调用,那么就会把”a“放入常量池,并返回”a“在常量池中的引用。

                    如果是new String("a").internal ,其中在 new String的时候上文已经说到过,会在堆和常量池各创建一个对象,那么这里返回的就是常量池的字符串a的引用。

                    如果是new StringBuilder("a").internal,其中new StringBuilder会在堆中创建一个对象,常量池没有,这里调用intern方法后,**会将堆中字串a的引用放到常量池,注意这里始终只是创建了一个对象,

                    返回的引用虽然是常量池的,但是常量池的引用是指向堆中字串a的引用的

    上述粗体字描述部分也就是不同之处,也是intern方法在Java6 之后的使用变化如何体现。

    再简单总结一下Java7和Java8的intern方法作用:如果常量池没有,那么会将堆中的字符串的引用放到常量池,注意是引用,然后返回该引用。为什么Java7和Java8会不一样呢,原因就是 Java7之后(部分虚拟机,Hotspot,JRockit)已经将永久代的

    常量池、静态变量移出,放入了Java堆中,而永久代也在Java8中完全废弃,方法区改名为元空间。既然常量池已经在Java6之后放入了堆中,那么如果堆中已经创建过此字符串的对象了,那么就没有必要在常量池中再创建一个一毛一样的对象了,直接将其引用拷贝

    返回就好了,因为都是处于同一个区域Java堆中。

     

    下面看几个实践:来验证一下上述的结论:

    1、下图 在new的时候已经创建了两个对象,第二行,只是获取的第一行创建的常量池的对象的引用,实际的对象已经创建过了。这里是两个不同的对象,返回false。

    2、和上述一样,只不过这一次第一行,现在常量池创建了对象,第二行发现常量池已经有了,只在堆上创建了一次对象,但仍然是两个对象,引用不同,返回false。

    3、第一行,Strignbuilder只会在堆中创建一个对象,第二行调用intern方法后,会将堆中的引用放到到常量池中。第三行发现常量池中已经有这个字符串的引用了,直接返回。因此是同一个引用,返回的都是第一次创建的堆中字串的引用

    4、和上述3的不同之处在于没有调用intern方法,因此结果输出不一样。

     

    5、new String之后使用 + 在Java中会进行编译优化,编译成字节码指令后,会将+ 优化成 先new Stringbuilder对象,然后调用append方法进行拼接。

    如下图:

    反编译生成字节码:

    因此这里s1最终创建的时候,xyzz字符串并没有在常量池创建,只是在堆中创建了,因为就如同上面的3一样,是new Stringbuilder操作。

    所以在调用intern操作后,将其堆中的引用放入常量池并返回。所以后面的结果都是true,因为至始至终都是堆中的一个对象。

    6、和上述5是相反的,结果输出也不同。

    总结:

     在Java6之后,使用intern可以起到优化的作用,但也要看具体的情况,比如在使用加号做字符拼接的时候,如果不想在因为其他的操作在常量池中重新创建相同的对象,那么调用intern方法,在常量池中只会放入一个引用,这时候只创建了一个对象。

     

  • 相关阅读:
    轻量级数据库sqlite的使用
    Integer引发的思考
    css限制显示行数
    数据库 chapter 17 数据仓库与联机分析处理技术
    数据库 chapter 15 对象关系数据库系统
    数据库 chapter 16 XML数据库
    数据库 chapter 14 分布式数据库系统
    数据库 chapter 11 并发控制
    数据库 chapter 12 数据库管理系统
    数据库 chapter 13 数据库技术新发展
  • 原文地址:https://www.cnblogs.com/wa1l-E/p/14216386.html
Copyright © 2020-2023  润新知