Java字符串的intern方法有何奥妙之处

学习背景

进入正文学习字符串的intern()方法之前,先给下这4个问题,看下自己是否都知道答案?

1、String s1 = “a” + “b”; //创建了几个对象?
2、String s2 = new String(“ab”); //创建了几个对象?
3、String s3 = new String(“a”) + new String(“b”); //创建了几个对象?
4、String s4= new String(“a”) + new String(“a”); s4.intern(); //创建了几个对象?

如果都清楚,恭喜你,大佬一枚,不用往下学习了,哈哈哈!
那如果不太确定或者需要加深自己的理解,建议进入正文一起来了解下吧!
当然,也可以拉到最后有答案!

String#intern()示例代码

先来执行一下String调用intern()方法的一段示例代码:

public class StringInternTest {
    public static void main(String[] args) {
        String reference1 = new String("a");
        reference1.intern();
        String reference2 = "a";
        System.out.println(reference1 == reference2);

        String reference3 = new String("a") + new String("a");
        reference3.intern();
        String reference4 = "aa";
        System.out.println(reference3 == reference4);
    }
}

JDK1.6 执行输出结果:

false
false

JDK1.7 执行输出结果:

false
true

大家可以先思考一下为什么结果是这样的?往下会具体介绍!

String##intern()源码

先来看一下intern()方法的JDK源码如下:

    /**
     * Returns a canonical representation for the string object.
     * <p>
     * A pool of strings, initially empty, is maintained privately by the
     * class {@code String}.
     * <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>
     * It follows that for any two strings {@code s} and {@code t},
     * {@code s.intern() == t.intern()} is {@code true}
     * if and only if {@code s.equals(t)} is {@code true}.
     * <p>
     * All literal strings and string-valued constant expressions are
     * interned. String literals are defined in section 3.10.5 of the
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * @return  a string that has the same contents as this string, but is
     *          guaranteed to be from a pool of unique strings.
     */
    public native String intern();

很显然通过源码可以看到intern()是一个native本地方法,但是native具体实现源码已经被隐藏了,这是一个历史故事了,SUN公司在JDK7开发期间,由于技术竞争和商业竞争陷入泥潭,无力再投入精力继续研发JDK,Oracle半路杀出直接收购Sun公司,Oracle接管JDK的研发后,发版了自己的Oracle JDK,Oracle的native底层等很多源码就被隐藏了,不过Oracle官方也声明OpenJDK和Oracle JDK7及以后版本,源码几乎是一模一样的,想要了解native底层源码具体实现过程,可以下载开源的OpenJDK的源码进行查看。

OpenJDK官网:https://hg.openjdk.java.net/
GitHub也开源啦:https://github.com/openjdk/jdk

例如String对应的OpenJDK底层源码主入口:jdk7\jdk\src\share\native\java\lang\String.c

Java_java_lang_String_intern(JNIEnv *env, jobject this)
{
    return JVM_InternString(env, this);
}

native底层方法的实现,需要掌握C和C++的语法,学习门槛要求比较高,这里不是我们要学习的重点,不做具体介绍。

String#intern()方法作用

前面JDK源码intern()方法的英文注释已经说明了intern()方法的有具体用途了,网上也有很多说明,不过这里我以个人的理解以及话术简单概括下intern()方法的作用如下:

(1)只要调用String对象的intern(),都会去找到字符串常量池,然后判断String对象的字符串内容是否已经存在常量池中,不存在,则往字符串常量池中创建该字符串内容的对象(JDK6及之前)或创建新的引用并指向堆区已有对象地址(JDK7之后),存在则直接返回。

(2)JDK7时,字符串常量池从永久代脱离,迁移到堆区中,相比于JDK6,变化不只是字符串常量池迁移到堆区而已,另一个变化就是调用字符串对象的intern()方法,如果字符串常量池中不存在该字符串内容的对象,则不会再像JDK6直接往字符串常量池中创建该字符串内容的对象,而是创建一个新的引用并指向堆区已有对象地址,实现字符串常量池和堆区字符串共用的目的,效率更高。

JDK6 String#intern()执行说明

一张图介绍前面示例代码JDK6执行过程如下:

/**
 * JDK6 String#intern()执行说明
 */
public class StringInternTest {
    public static void main(String[] args) {
        //Step6.1
        //创建了2个对象,分别是堆区的String对象和字符串常量池中的"a"对象,reference1引用指向在堆区中的对象地址
        String reference1 = new String("a");
        //Step6.2
        //判断字符串常量池,是否该字符串"a",此前,池中已经有该对象了,因此会返回池中的对象地址的引用
        reference1.intern();
        //Step6.3
        //字符串常量池中已存在字符串"a",因此reference2引用直接指向对象在字符串常量池中的地址
        String reference2 = "a";
        //reference1指向对象地址是在堆区,reference2指向对象地址是在永久代的常量池,显然不可能一样
        System.out.println(reference1 == reference2);

        //Step6.4
        //创建了2个对象,分别是在堆区的String对象(内容是"aa")和字符串常量池中的"a"对象
        //reference3引用指向对象在堆区中的地址,这过程还会在堆区创建了两个无引用的"a"对象,这里不做讨论
        String reference3 = new String("a") + new String("a");
        //Step6.5
        //判断永久代中的字符串常量池,是否存在该字符串"aa",这里是首次出现,因此直接将字符串拷贝并放到池中
        reference3.intern();
        //Step6.6
        //池中已存在该字符串,reference2引用直接指向对象在永久代字符串常量池中的地址
        String reference4 = "aa";
        //同样,reference3指向堆区地址,reference4指向永久代常量池中的地址,显然不可能一样
        System.out.println(reference3 == reference4);
    }
}

JDK7 String#intern()执行说明

一张图介绍前面示例代码JDK7执行过程如下:

/**
 * JDK1.7 String#intern()执行说明
 **/
public class StringInternTest {
    public static void main(String[] args) {
        //Step7.1
        //创建了2个对象,分别是堆区的String对象和字符串常量池中的"a"对象,reference1引用指向在堆区中的对象地址
        String reference1 = new String("a");
        //Step7.2
        //判断字符串常量池,是否该字符串"a",此前,池中已经有该对象了,因此会返回池中的对象地址的引用
        reference1.intern();
        //Step7.3
        //字符串常量池中已存在字符串"a",因此reference2引用直接指向对象在字符串常量池中的地址
        String reference2 = "a";
        //reference1指向对象地址是在堆区,reference2指向对象地址是在堆区的字符串常量池,引用指向的对象地址不一样
        System.out.println( reference1 == reference2);

        //Step7.4
        //创建了2个对象,分别是在堆区的String对象(内容是"aa")和字符串常量池中的"a"对象(注意并不会创建"aa"对象)
        //reference3引用指向对象在堆区中的地址,这过程还会在堆区创建了两个无引用的"a"对象,这里不做讨论
        String reference3 = new String("a") + new String("a");
        //Step7.5
        //判断堆区的字符串常量池中,是否存在该字符串"aa",显然这里是首次出现
        //但并不像JDK6会新建对象"aa"存储,而是存储指向堆区已有对象地址的一个新引用
        reference3.intern();
        //Step7.6
        //指向池中已有该字符串的新引用,reference4引用直接指向字符串常量池中的这个新引用,新引用则指向堆区已有对象地址
        String reference4 = "aa";
        //reference4指向新引用,而新引用则指向堆区已有对象地址,跟reference3引用直接指向的对象地址是同一个
        System.out.println(reference3 == reference4);
    }

经典面试问题之创建了几个对象?

在实际的Java面试当中,经常会被问到字符串创建了几个对象的问题,主要是考察学习者对于对象的实例化以及字符串常量池在JVM结构体系中是如何运行的,个人觉得比较常见问题,无法就是如下几个:

1、最简单的比如:String s1 = “a” + “b”;创建了几个对象?

答:最多1个,多个字符串常量相加会被编译器优化为一个字符串常量即"ab",如果字符串常量池不存在,则创建该对象。

2、相对简单的比如:String s1 = new String(“ab”);创建了几个对象?

答:1个或2个,使用new实例化对象,必然会在堆区创建一个对象,另外一个就是如果在字符串常量池中不存在"ab"这个对象,则会创建这个"ab"常量对象。

3、稍微难一点的比如:String s2 = new String(“a”) + new String(“b”);创建了几个对象?

答:至少4个,最多6个
堆区的1个new StringBuilder()和2个new String()
还有1个是StringBuilder()的toString()方法底层实现是new String(value, 0, count)
另外2个即"a"、"b"可能会在常量池新建对象
有的同学可能会有疑问,那这个toString过程"ab"字符串不会在常量池中也创建吗?
答案是,不会,最后StringBuilder的toString() 的调用,底层new String(value, 0, count) 并不会在字符串常量池中去创建"ab"对象。
两个new String相加会被优化为StringBuilder,可以通过javac和javap查看汇编指令如下:
javac InternTest.java
javap -c InternTest

public class com.justin.java.lang.InternTest {
  public com.justin.java.lang.InternTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/StringBuilder
       3: dup
       4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
       7: new           #4                  // class java/lang/String
      10: dup
      11: ldc           #5                  // String a
      13: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
      16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: new           #4                  // class java/lang/String
      22: dup
      23: ldc           #8                  // String b
      25: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
      28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      34: astore_1
      35: return
}

最难的无非就是再调用intern()方法,比如:
String s3= new String(“a”) + new String(“b”);
s3.intern();创建了几个对象?

答:最少4个,最多7个
1个new StringBuilder()和2个new String
还有1个是StringBuilder()的toString()方法底层实现是new String(value, 0, count)
另外"a"、“b"可能会在常量池新建对象
最后调用intern()方法时,会去字符串常量池,判断"ab"是否存在,不存在,JDK6时会创建"ab” 1个对象,JDK7则只创建"ab"的引用并指向堆区内容为"ab"的StringBuilder对象地址。

到此这篇关于Java字符串的intern方法有何奥妙之处的文章就介绍到这了,更多相关Java intern方法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java String的intern方法

    首先我们应该清楚的是JDK1.6和JDK1.7中String类的intern方法还是有差别的: JDK1.6中的intern:   调用intern方法的时候首先会去常量池中查看是否存在与当前String值相同的值,如果存在的话,则直接返回常量池中这个String值的引用:如果不存在的话,则会将原先堆中的该字符串拷贝一份到常量池中. JDK1.7中的intern:   调用intern方法的时候首先会去常量池中查看是否存在与当前String值相同的值,如果存在的话,则直接返回常量池中这个Stri

  • 通过String.intern()方法浅谈堆中常量池

    简介 String是我们最常用的一个类,和普通java类一样其对象会存在java堆中.但是String类有其特殊之处,可以通过new方法生成,也可以通过带引号的字符串常量直接赋值.在JDK7之前,字符串常量是存在永久带Perm 区的,JDK7开始在将常量池迁移到堆中,这个变化也导致了String的新特性,下面我们慢慢进行介绍. String.intern()方法 简单的说,String.intern()方法的作用就是返回常量池中字符串对象,在对该方法进行详解之前,我们看几个创建字符串对象的例子.

  • 关于java String中intern的深入讲解

    序 本文主要研究一下java String的intern String.intern() java.base/java/lang/String.java public final class String implements java.io.Serializable, Comparable<String>, CharSequence, Constable, ConstantDesc { //...... /** * Returns a canonical representation for

  • Java String的intern方法使用场景示例

    在讲intern方法前,我们先简单回顾下Java中常量池的分类. 常量池的分类 Java中常量池可以分为Class常量池.运行时常量池和字符串常量池. 1. Class文件常量池 在Class文件中除了有类的版本.字段.方法.接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用. 所谓字面量类似与我们平常说的常量,主要包括以下两种 文本字符串,例如String a = "aa".其中"aa"就是字

  • String中intern方法的使用场景详解

    在讲intern方法前,我们先简单回顾下Java中常量池的分类. 常量池的分类# Java中常量池可以分为Class常量池.运行时常量池和字符串常量池. 1. Class文件常量池 在Class文件中除了有类的版本.字段.方法.接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用. 所谓字面量类似与我们平常说的常量,主要包括以下两种 文本字符串,例如String a = "aa".其中"aa"就是

  • Java字符串的intern方法有何奥妙之处

    学习背景 进入正文学习字符串的intern()方法之前,先给下这4个问题,看下自己是否都知道答案? 1.String s1 = "a" + "b"; //创建了几个对象? 2.String s2 = new String("ab"); //创建了几个对象? 3.String s3 = new String("a") + new String("b"); //创建了几个对象? 4.String s4= new

  • 详解Java String中intern方法的原理与使用

    目录 简介 常量池简介 intern方法简介(JDK7) 原理(JDK6与JDK7) 例程测试 例程分析 jdk1.6 jdk1.7 应用实例 简介 本文介绍Java的String的intern方法的原理. 常量池简介 在 JAVA 语言中有8种基本类型和一种比较特殊的类型String.这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池(在方法区)的概念.常量池就类似一个JAVA系统级别提供的缓存.8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊. Str

  • java中String.intern()方法功能介绍

    下文笔者讲述java中String.intern()方法的功能简介说明,如下所示: String.intern原理 String.intern():    此方法是一个Native方法      底层调用C++的 StringTable::intern方法实现 当通过语句str.intern()调用intern()方法后    JVM 就会在当前类的常量池中查找是否存在与str等值的String     若存在则直接返回常量池中相应Strnig的引用     若不存在,则会在常量池中创建一个等值

  • java字符串的截取方法substring()代码解析

    这篇文章主要介绍了java字符串的截取方法substring()代码解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 返回位于 String 对象中指定位置的子字符串. public class StringTest { public static void main(String[] args) { String name="jason"; System.out.println(name.substring(0,1)); //结果

  • JAVA字符串拼接常见方法汇总

    字符串的拼接,常使用到的大概有4种方式: 1.直接使用"+"号 2.使用String的concat方法 3.使用StringBuilder的append方法 4.使用StringBuffer的append方法 由于String是final类型的,因此String对象都是属于不可变对象,因此,在需要对字符串进行修改操作的时候(比如字符串的连接或者是替换),String总是会生成新的对象. 1."+" 如果不考虑其他,使用"+"号来连接字符串无疑是最

  • 使用位运算、值交换等方式反转java字符串的多种方法(四种方法)

    在本文中,我们将向您展示几种在Java中将String类型的字符串字母倒序的几种方法. StringBuilder(str).reverse() char[]循环与值交换 byte循环与值交换apache-commons-lang3 如果是为了进行开发,请选择StringBuilder(str).reverse()API.出于学习的目的,我们可以研究char[]和byte方法,其中涉及到值互换和移位运算技术,这些技术对于了解StringBuilder(str).reverse()API黑匣子背后

  • Java字符串拼接新方法 StringJoiner用法详解

    Java中如何输出像1-2-3-4-5 这样的字符 抱歉对于这个问题我甚至不能想到一个合适的标题,但是不重要 以下操作基于 jdk 1.8 StringJoiner sj = new StringJoiner("-", "", ""); sj.add("1").add("1").add("2"); String desiredString = sj.toString(); 在1.8版本中

  • Java字符串逆序方法详情

    目录 1.简述 2.代码实现 1.简述 记录下实现字符串逆序的两种方法: 第一种方法比较暴力,通过字符串的下标将字符串逆转过来,这里要用到String类的substring()方法,这个方法比较常用,就不仔细写了 第二中方法是将String类转换成StringBuffer类,通过调用StringBuffer类的reverse()方法将字符串逆转,这个方法比较简单 下面是两种方法的实现代码: public class test_2_13 {     public static void main(

随机推荐