学习背景

进入正文学习字符串的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_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: new           #2                  // class java/lang/StringBuilder3: dup4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V7: new           #4                  // class java/lang/String10: dup11: ldc           #5                  // String a13: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;19: new           #4                  // class java/lang/String22: dup23: ldc           #8                  // String b25: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V28: 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_135: return
}

6、最难的无非就是再调用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--深入理解字符串的String#intern()方法奥妙之处相关推荐

  1. string.intern()方法理解

    String.intern()方法介绍 字符串常量池 在jdk7中,字符串常量池和静态变量都存储在堆中. 直接使用双引号声明出来的String对象会直接存储在常量池中. 如果不是用双引号声明的Stri ...

  2. 【Java 基础】字符串(String、StringBuilder),日期(Date、SimpleDateFormat、Calendar)

    字符串.日期 字符串(String) 字符串常量池(String Constant Pool) 字符串的初始化 intern 方法 字符串的常用方法(截取) 可变字符串(StringBuilder) ...

  3. Nacos源码中为什么使用了String.intern方法?

    前言 面试的时候经常被问到String的intern方法的调用及内存结构发生的变化.但在实际生产中真正用到过了吗,看到过别人如何使用了吗? 最近阅读Nacos的源码,还真看到代码中使用String类的 ...

  4. String.intern()方法JDK6与JDK7/JDK8不同

    在JDK6中,String.intern()方法先去运行时常量池中查看有无该字符串,如果有,则直接返回该字符串在方法区的内存地址.如果没有则会先将该字符串对象复制一份保存在常量池中,并返回该字符串对象 ...

  5. 谈谈String.intern方法

    谈谈String.intern方法 1. 首先明确什么是intern()方法? String.intern()是一个Native方法,底层调用C++的 StringTable::intern方法实现. ...

  6. String.intern() 方法

    字符串常量池: jdk6中字符串常量池在永久代,从jdk7开始,在堆中又划分了一块区域,放到了堆空间中. intern() 方法: 是扩充常量池的一个方法,当一个String的实例调用intern() ...

  7. Java文件类字符串getAbsolutePath()方法(带示例)

    文件类字符串getAbsolutePath() (File Class String getAbsolutePath()) This method is available in package ja ...

  8. Java 中去除字符串中空格的方法

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 1.方法分类 str.trim(); //去掉首尾空格 str.replace(" &qu ...

  9. Java中去除字符串中空格的方法

    昨天写了一个关于Excel文件处理的脚本,在字符串匹配功能上总是出现多余不正确的匹配,debug调试之后,发现一个坑. ------->代码中字符串使用了replaceAll()方法,去除了所有 ...

最新文章

  1. 为了探究不同光照处理_渭南市实验初中“诱思探究学导”课堂教学改革展示活动圆满成功...
  2. H. Fight Against Monsters
  3. SIGIR 2021 | 基于用户偏好感知的虚假新闻检测
  4. 深入浅出之string
  5. 构建之法第二章读后感
  6. Python网络爬虫第二弹《http和https协议》
  7. Android—EventBus使用与源码分析
  8. 穷人最缺少的是什么?
  9. CEF3—在网页加载前给js对象填值
  10. 关于Js下拉导航的解释
  11. linux内核arc4算法,linux内核中与进程相关的数据结构(基于linux-mainline-rc4)
  12. java编写布局文件_鸿蒙OS利用JAVA编写的布局实践练习
  13. oem718d 基准站设置_千寻cors账号参数设置完成,RTK手簿显示浮动或单点伪距,能不能进行测量?...
  14. 项目开发中对使用的第三方库统一进行管理__添加属性表/页
  15. [机器学习]关联挖掘算法Apriori和FP-Growth以及基于Spark 实例
  16. c语言早期标准没有数据转换,标准C语言2
  17. gmt绘制江苏省高程异常图
  18. RemObjects Remoting SDK-SEO狼术
  19. SpringSecurity授权管理介绍
  20. camera基础概念之等效焦距 视场角的计算

热门文章

  1. android视频动态壁纸app,手机壁纸视频动态壁纸
  2. 波士顿房价预测python决策树_波士顿房价预测 - 最简单入门机器学习 - Jupyter
  3. VR技术给我们的生活带来哪些影响
  4. PDF编辑方法,怎么把PDF其中一页删除
  5. 4.1.1 网络层的功能(路由选择与分组转发、异构网络互连、拥塞控制)
  6. 拼多多是如何做用户增长的?
  7. iphone控制中心自定义没有计算机,如何在iPhone上自定义iOS 11控制中心功能
  8. 计算机视觉(十一):Keras Pipline与自定义模型
  9. 爬虫基本概念(新手必看)
  10. pwm调速c语言,PWM调速的C语言程序编写