文章目录

  • 一、前言
  • 二、图文理解String创建对象
    • 1.例子一
    • 2.例子二
    • 3.例子三
    • 4.例子四
    • 5.例子五
    • 6.例子六
  • 三、深入理解intern()方法
    • 1. 源码查看
    • 2. 例子一
    • 3. 例子二
    • 4. 例子三
    • 5. 例子四
    • 6. 例子五
    • 7. 例子六
  • 四、总结

一、前言

String字符串在我们日常开发中最常用的,当然还有他的两个兄弟StringBuilder和StringBuilder。他三个的区别也是面试中经常问到的,大家如果不知道,就要先去看看了哈!最近也是看周志明老师的深入JVM一书中写到关于intern()方法的介绍,小编也是以前没在开发中用到。但是面试题还是很多的,所以特意研究了一天,写下来记录一下自己的收获,希望也可以帮助到大家!!

二、图文理解String创建对象

1.例子一

String str1 = "wang";

JVM在编译阶段会判断字符串常量池中是否有 “wang” 这个常量对象如果有,str1直接指向这个常量的引用,如果没有会在常量池里创建这个常量对象。

2.例子二

String str2 = "学" + "Java";

JVM编译阶段过编译器优化后会把字符串常量直接合并成"学Java",所有创建对象时只会在常量池中创建1个对象。

3.例子三

String str3 = new String("学Java");

当代码执行到括号中的"学Java"的时候会检测常量池中是否存在"学Java"这个对象,如果不存在则在字符串常量池中创建一个对象。当整行代码执行完毕时会因为new关键字在堆中创建一个"学Java"对象,并把栈中的变量"str3"指向堆中的对象,如下图所示。这也是为什么说通过new关键字在大部分情况下会创建出两个字符串对象!

4.例子四

String str4 = "学Java";
String str5 = "学Java";
System.out.println(str4 == str5); // 如下图得知为:true

第一行代码:
JVM在编译阶段会判断字符串常量池中是否有 “学Java” 这个常量对象如果有,str4直接指向这个常量的引用,如果没有会在常量池里创建这个常量对象。
第二行代码:
再创建"学Java",发现字符串常量池中存在了"学Java",所以直接将栈中的str5变量也指向字符串常量池中已存在的"学Java"对象,从而避免重复创建对象,这也是字符串常量池存在的原因。

5.例子五

String str6 = new String("学") + new String("Java");

首先,会先判断字符串常量池中是否存在"学"字符串对象,如果不存在则在字符串常量池中创建一个对象。当执行到new关键字在堆中创建一个"学"字符串对象。后面的new String(“Java”),也是这样。
然后,当右边完成时,会在堆中创建一个"学Java"字符串对象。并把栈中的变量"str6"指向堆中的对象。
总结:一句代码创建了5个对象,但是有两个在堆中是没有引用的,按照垃圾回收的可达性分析,他们是垃圾就是"学"、"Java"这俩垃圾

心得:
上面代码进行反编译:

String str6 = (new StringBuilder()).append(new String("\u5B66")).append(new String("Java")).toString();

底层是一个StringBuilder在进行把两个对象拼接在一起,最后栈中str6指向堆中的"学Java",其实是StringBuilder对象。

6.例子六

String str7 = new String("学Java");
String str8 = new String("学Java");
System.out.println(str7 == str8); // 如下图得知为:false

执行到第一行:
执行到括号内的"学Java",会先判断字符串常量池中是否存在"学Java"字符串对象,如果没有则在字符串常量池中创建一个"学Java"字符串对象,执行到new关键字时,在堆中创建一个"学Java"字符串对象,栈中的变量str7的引用指向堆中的"学Java"字符串对象。
执行到第二行:
当执行到第二行括号中的"学Java"时,先判断常量池中是否有"学Java"字符串对象,因为第一行代码已经将其创建,所以有的话就不创建了;执行到new关键字时,在堆中创建一个"学Java"字符串对象,栈中的变量str8的引用指向堆中的"学Java"字符串对象。

三、深入理解intern()方法

1. 源码查看

public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {// ..../*** 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()方法被调用的时候,如果字符串常量池中已经存在这个字符串对象了,就返回常量池中该字符串对象的地址;如果字符串常量池中不存在,就在常量池中创建一个指向该对象堆中实例的引用,并返回这个引用地址。

2. 例子一

我们直接先把周志明老师的在深入JVM一书的例子:

String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);

这段代码在JDK 6中运行,会得到两个false,而在JDK 7、8中运行,会得到一个true和一个false。产 生差异的原因是,在JDK 6中,intern()方法会把首次遇到的字符串实例复制到永久代的字符串常量池 中存储,返回的也是永久代里面这个字符串实例的引用,而由StringBuilder创建的字符串对象实例在 Java堆上,所以必然不可能是同一个引用,结果将返回false。 而JDK 7(以及部分其他虚拟机,例如JRockit)的intern()方法实现就不需要再拷贝字符串的实例到永久代了,既然字符串常量池已经移到Java堆中,那只需要在常量池里记录一下首次出现的实例引用即可,因此intern()返回的引用和由StringBuilder创建的那个字符串实例就是同一个。而对str2比较返 回false,这是因为==“java”(下面解释)==这个字符串在执行String-Builder.toString()之前就已经出现过了,字符串常量 池中已经有它的引用,不符合intern()方法要求“首次遇到”的原则,“计算机软件”这个字符串则是首次出现的,因此结果返回true。

java为什么已经存在了?

1.我们在一个类中输入System,然后点击到这个方法中,方法内容如下:

public final class System {// ...private static void initializeSystemClass() {// ...sun.misc.Version.init();// ...}// ...
}

2.我们点击上面的Version类,类内容如下:

public class Version {private static final String launcher_name = "java";private static final String java_version = "1.8.0_121";private static final String java_runtime_name = "Java(TM) SE Runtime Environment";private static final String java_profile_name = "";private static final String java_runtime_version = "1.8.0_121-b13";private static boolean versionsInitialized;private static int jvm_major_version;private static int jvm_minor_version;private static int jvm_micro_version;private static int jvm_update_version;private static int jvm_build_number;private static String jvm_special_version;private static int jdk_major_version;private static int jdk_minor_version;private static int jdk_micro_version;private static int jdk_update_version;private static int jdk_build_number;private static String jdk_special_version;private static boolean jvmVersionInfoAvailable;public Version() {}public static void init() {System.setProperty("java.version", "1.8.0_121");System.setProperty("java.runtime.version", "1.8.0_121-b13");System.setProperty("java.runtime.name", "Java(TM) SE Runtime Environment");}
}

3.找到java关键字,所以上面的str2.intern() == str2返回false。

private static final String launcher_name = "java";

我们开始例子和详细解释,发车了,大家坐好哦!
以下例子来自:原博客,解释是为小编自己的理解。

3. 例子二

String str1 = new String("wang");
str1.intern();
String str2 = "wang";
System.out.println(str1 == str2); // false

执行第一行代码:
首先执行到"wang",因为字符串常量池中没有,则会在字符串常量池中创建"wang"字符串对象。
然后执行到new关键字时,在堆中创建一个"wang"的对象,并把栈中的str1的引用指向"wang"对象。

执行第二行代码:
这里我们看到就是str1手动把"wang"放在字符串常量池中,但是发现字符串常量池中已经存在"wang"字符串对象,所以直接把已存在的引用返回。虽然str1.intern()指向了字符串常量池中的"wang",但是我们第四行代码并没有拿str1.intern()作比较,所以还是false。

执行第三行代码:
首先通过第一行代码,字符串常量池中已经有"wang"字符串对象了,所以本行代码只需要把栈中的str2变量指向字符串常量池中的"wang"即可。

执行第四行代码:
如上和下图可见,我们的str1执行堆中的"wang",str2指向的是字符串常量池中的"wang",肯定返回false。

4. 例子三

String str3 = new String("wang") + new String("zhen");
str3.intern();
String str4 = "wangzhen";
System.out.println(str3 == str4); // true

执行到第一行代码:
首先执行到"wang"时,因为字符串常量池中没有,则会在字符串常量池中创建一个"wang"字符串对象;
然后执行到"zhen"时,因为字符串常量池中没有,则会在字符串常量池中创建一个"zhen"字符串对象;
最后执行到new关键字时,看到是两个,但是底层字节码文件反编译的是使用如下可见,只会有一个StringBuilder对象生成,于是将栈中的str3的引用指向堆中的"wangzhen"对象。

String str3 = (new StringBuilder()).append(new String("wang")).append(new String("zhen")).toString();

执行到第二行代码:
这里我们看到就是str3手动把"wangzhen"放在字符串常量池中,在字符串常量池中没有找到"wangzhen",于是把str3 .intern()引用指向堆中的"wangzhen"的地址。现在str3和str3 .intern()一样

执行到第三行代码:
判断字符串常量池中是否存在"wangzhen"字符串对象,第二行代码已经在字符串常量池中创建了"wangzhen",不过str4是指向str3中堆的引用(看图就明白了)。

执行到第四行代码:
str3和str3 .intern()引用一样,str3 .intern()和str4是一个,所以str3和str4相等。

5. 例子四

String str5 = new String("wang") + new String("zhen");
String str6 = "wangzhen";
str5.intern();
System.out.println(str5 == str6); // false

执行到第一行代码:
首先执行到"wang"时,因为字符串常量池中没有,则会在字符串常量池中创建一个"wang"字符串对象;
然后执行到"zhen"时,因为字符串常量池中没有,则会在字符串常量池中创建一个"zhen"字符串对象;
最后执行到new关键字时,看到是两个,但是底层字节码文件反编译的是使用如下可见,只会有一个StringBuilder对象生成,于是将栈中的str5的引用指向堆中的"wangzhen"对象。同上面的反编译代码

执行到第二行代码:
执行到"wangzhen",判断字符串常量池中是否存在"wangzhen",发现没有,在字符串常量池中创建"wangzhen"字符串对象,然后把栈中的str6变量的引用指向"wangzhen"对象。

执行到第三行代码:
这里我们看到就是str5手动把"wangzhen"放在字符串常量池中,我们发现,在字符串常量池中已存在"wangzhen",于是str5 .intern()就是"wangzhen"对象的地址。我们还没没有收到返回值

如下图,我们看到肯定返回false,此时str5.intern() == str6 (true)

6. 例子五

String str7 = new String("wang") + new String("zhen");
String str8 = "wangzhen";
System.out.println(str7.intern() == str8); // true
System.out.println(str7 == str8); // false
System.out.println(str8 == "wangzhen"); // true

执行到第一行代码:
同例子三和例子四的第一行代码;

执行到第二行代码:
先判断字符串常量池中是否存在"wangzhen"对象,发现没有,我们在字符串常量池中创建"wangzhen"字符串对象;

执行到第三行代码:
执行到str7.intern()这里,我们看到就是str7手动把"wangzhen"放在字符串常量池中,在字符串常量池中已结存在"wangzhen",于是把字符串常量池"wangzhen"的地址。现在str7和str7 .intern()一样

执行到第四行代码:
str7的指向为堆中的"wangzhen",而str8指向则为字符串常量池中的"wangzhen",故不相同,返回false。

执行到第五行代码:
str8指向则为字符串常量池中的"wangzhen",执行"wangzhen",则把已存在的字符串常量池中"wangzhen"返回,故相同,返回true。

7. 例子六

String str9 = new String("wang") + new String("zhen");
System.out.println(str9.intern() == str9); // true
System.out.println(str9 == "wangzhen"); // true

执行到第一行代码:
同上

执行到第二行代码:
执行到str9.intern()这里,我们看到就是str9手动把"wangzhen"放在字符串常量池中,在字符串常量池中没有"wangzhen",于是把str3 .intern()引用指向堆中的"wangzhen"的地址。现在str9和str9.intern()一样

执行到第三行代码:
str9指向堆内存中的"wangzhen",执行到"wangzhen"时,发现字符串常量池中已存在,直接返回str9指向的引用即可,故返回true。

四、总结

经过这么多例子,大家应该明白了吧,还是要自己跟着例子进行换一下jvm内存图,这样就理解记忆,也不会轻易忘记!看到这里了,给小编点个赞呗,整理了一天。太不容易了,谢谢大家了!

在此感谢小编参考的博客:参考博客,小编在基础上按照自己的理解写的,也进行了深入的扩展哈!


有缘人才能看到,自己网站,欢迎访问!!!

点击访问!欢迎访问,里面也是有很多好的文章哦!

JDK8中String的intern()方法详细解读【内存图解+多种例子+1.1w字长文】相关推荐

  1. Java中String类intern()详解

    1.背景 在开发过程中很多朋友,由于不会正确使用intern(),导致开发的程序,执行效率比较差.同时最近发现一道非常有意思的关于intern()的面试题,这道面试题还是有不小的难度,相信很多朋友看到 ...

  2. Java之String系列--intern方法的作用及原理

    原文网址:Java之String系列--intern方法的作用及原理_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Java的String的intern方法的原理. 常量池简介 在 JAVA 语言中 ...

  3. String的intern方法演示及各种字符串的拼接对比

    演示String的intern方法,以及各种拼接字符串的区别 package string;/*** @Author gzx* @create 2022-1-14 jdk8*/ public clas ...

  4. Java String的intern方法

    1. 首先String不属于8种基本数据类型,String是一个对象. 因为对象的默认值是null,所以String的默认值也是null:但它又是一种特殊的对象,有其它对象没有的一些特性. 2. ne ...

  5. [转]String 之 new String()和 intern()方法深入分析

    引入 String,是 Java 中除了基本数据类型以外,最为重要的一个类型了.很多人会认为他比较简单.但是和 String 有关的面试题有很多,下面我随便找两道面试题,看看你能不能都答对: Q1:S ...

  6. c++中string的assign方法使用

    c++中string的assign方法使用 string的实际.h和.cpp文件是basic_string.h 和basic_string.tcc,所以string中assign也在这两个文件声明和定 ...

  7. JAVA中String的split方法

    我的个人网站: http://riun.xyz 以下源码版本:JDK1.8 简介 Java 中 String 的 split 方法可以将字符串根据指定的间隔进行切割,例如字符串 str = " ...

  8. java String的intern()方法

    本文已参与「新人创作礼」活动,一起开启掘金创作之路. 本文章所用jdk版本为jdk1.8 先看第一个例子 public class HelloWorld {public static void mai ...

  9. java intern_java String的intern方法

    我们知道再jvm的运行时内存可以分为堆.方法区.程序计数器.虚拟机栈和本地方法栈.而在方法区中有一个字符串常量池,用来保存字符串这个不可变量.如果我们使用String str=new String(& ...

最新文章

  1. 三个优秀的语义分割框架 PyTorch实现
  2. linux 同步方法剖析,Linux 同步方法剖析
  3. php5.5CURL图片上传废弃@
  4. noip模拟赛 蒜头君的兔子
  5. MySQL系列:性能优化
  6. c语言二进制转十进制(附完整源码)
  7. 您的浏览器没有获得Java Virtual Machine(JVM)支持。可能由于没有安装JVM或者已安装但是没有启用。请安装JVM1.5或者以上版本,如果已安装则启用它。...
  8. 5G赋能中国智慧教育
  9. linux双系统启动项grub,grub双系统启动顺序更改
  10. 直播网站源码,简洁的登录页面
  11. 微信小程序笔记——处理小程序页面栈限制(小程序wx.navigateTo封装)
  12. 建立SIP软电话环境
  13. arm920t架构cpu详解
  14. 《Gossip Girl》情侣布莱克·莱弗利(Blake Lively) 和佩恩·贝格利(Penn Badgley)分手
  15. 丧心病狂的前端冷知识
  16. 关注奢交所兄弟品牌佰家当 17年积累撬动万亿民资市场
  17. SSL2668 2017年8月7日提高组T1 根(dfs)
  18. 实现页面上方实现输入关键字搜索出关联关键词内容的功能
  19. centos安装gcc的方法
  20. 宇宙为什么没有单独存在的夸克,强行将夸克拉出来会怎样?

热门文章

  1. 15k的php会什么,【后端开辟】15k的php须要控制什么手艺
  2. unity3d发布安卓出错plese set the package name
  3. 青少年编程 中国电子学会scratch等级考试二级历年真题解析【持续更新 已更新至2023年3月】
  4. 怎么免费做百度引流?百度免费引流方法有哪些?
  5. python并发编程之semaphore(信号量)_python 之 并发编程(守护进程、互斥锁、IPC通信机制)...
  6. 一文带你了解APS生产计划排程系统
  7. spring框架_IOC_DI_AOP_MVC
  8. Arcgis教程:如何批量统计网格内的线段长度。
  9. 计算机中硬盘和移动硬盘的区别,笔记本硬盘和移动硬盘有什么区别
  10. 剖析Spring源码:加载IOC容器