String的intern()方法详解

文章目录


前言

在开发过程中很多朋友,由于不会正确使用intern(),导致开发的程序,执行效率比较差。同时最近发现一道非常有意思的关于intern()的面试题,这道面试题还是有不小的难度,相信很多朋友看到以后也不知道怎么解答,所以今天咱们深入详解下intern()。

Returns a canonical representation for the string object.

A pool of strings, initially empty, is maintained privately by the class String.

When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.

All literal strings and string-valued constant expressions are interned. String literals are defined in section 3.10.5 of the The Java ™ Language Specification.


当调用intern()时,如果池子里已经包含了一个与这个String对象相等的字符串,正如equals(Object)方法所确定的,那么池子里的字符串会被返回。否则,这个String对象被添加到池中,并返回这个String对象的引用。
注意:这里说的池子就是字符串常量池,大白话就是,调用intern()后,如果String对象的值如果在字符串常量池中,直接返回常量池中的地址,否则这个String对象将被添加到字符串常量池中,并返回字符串常量池中的地址。
由此可见,对于任何两个字符串s和t,当且仅当s.equals(t)为真时,s.intern() == t.intern()为真。
所有字面字符串和以字符串为值的常量表达式都是interned。


一、new String()创建了几个对象?

cpp 复制代码
 public static void main(String[] args) {
 		 // 2个对象
 		// 对象1:new String()的时候会在字符串常量池创建一个"abc"的字符串
 		// 对象2:在堆空间创建一个a对象
            String a=new String("abc");
            
         // 1个对象
         // 在字符串常量池创建一个"ab"的字符串对象   
            String b="ab";
    }

jvm内存结构分为方法区、堆、java虚拟机栈、本地方法栈、程序计数器等。当我们执行上面方法时。

  1. 首先将当前main方法压入java虚拟机栈的栈帧中。
  2. 在编译期的时候jvm会帮我们创建局部变量表,也就是我们当前方法的局部变量表。
  3. 局部变量有两个变量a和b,
    局部变量a的地址指向的是堆空间的对象a,对象a指向的是字符串常量池的"abc"
    局部变量b的地址指向的是字符串常量池的"ab";

二、Stting a=new String("ab")+new String("c")创建了几个对象

cpp 复制代码
 public static void main(String[] args) {
			// 6个对象
			// 对象1:new String("ab") 在堆空间创建一个对象
			// 对象2:new String("ab") 在字符串常量池创建"ab"
			// 对象3:new String("c") 在堆空间创建一个对象
			// 对象4:new String("c") 在字符串常量池创建"c"
			// 对象5:创建StringBuffer对象,在我们进行符串拼接的过程中,java底层会使用StringBuffer使用append进行拼接
			// 对象6:最后会调用StringBuffer的tostring方法在堆空间创建abc对象
            String abc=new String("ab")+new String("c")
                    
    }

在上述例子当中我们一共创建了6个对象。可能大家都好奇,字符串"abc"怎么没有去创建。因为在我们StringBuffer中,它会单独维护一个char数组去创建我们拼接的字符串,所以这里就不会去字符串常量池申请"abc"


三、String的intern()方法

当调用intern()时,如果池子里已经包含了一个与这个String对象相等的字符串,正如equals(Object)方法所确定的,那么池子里的字符串会被返回。否则,这个String对象被添加到池中,并返回这个String对象的引用。

注意:这里说的池子就是字符串常量池,大白话就是,调用intern()后,如果String对象的值如果在字符串常量池中,直接返回常量池中的地址,否则这个String对象将被添加到字符串常量池中,并返回字符串常量池中的地址。

cpp 复制代码
    public static void main(String[] args) {
        String a=new String("abc");
        String b="abc";
        System.out.println(a==b); // false


        String a1=new String("abcd").intern();
        String b1="abcd";
        System.out.println(a1==b1); // true

    }

第一个案例是false,其实在上面已经和大家解释过了,第一个a指向的是堆空间的a对象(字符串"abc存储在a对象当中"),第二个b指向的是字符串常量池的"abc" 所以是false。

第二个案例是true,因为当我们创建a1时,它首先还是会先在堆空间创建a对象,然后在字符串常量池创建"abcd"字符串,堆空间的a对象指向字符串常量池的"abcd"。因为我们调用了intern()方法,所以在这里它会直接返回的是字符串常量池的"abcd"地址。 b1则是直接指向字符串常量池的"abcd",所以返回结果是true。

cpp 复制代码
    public static void main(String[] args) {
        String a = new String("ab") + new String("c");
        String b = "abc";
        System.out.println(a == b); // false

        String a1 = new String("ab") + new String("cd");
        a1.intern();
        String b1 = "abcd";
        System.out.println(a1 == b1); // true
    }

在上述这个案例中,第一个打印的是false第二个打印的是true。

第一个案例false,其实在上面已经和大家解释过了,第一个a指向的是堆空间的a对象(字符串"abc存储在a对象当中"),第二个b指向的是字符串常量池的"abc" 所以是false。

第二个案例是true,因为在这个案例中,我们a1指向的是堆空间的a1对象,a1对象里面存储了我们的"abcd",当我们调用intern()方法时,它会去字符串常量池创建一个对象,然后把当前对象的地址指向我们的a1的堆空间地址。(这里会有点绕,因为我们已经创建了字符串"abcd",所以为了节省空间。就不会在去字符串常量池去申请一个"abcd",而是直接创建一个对象指向我们堆空间的a1对象)


四:面试题

有了对以上的知识的了解,我们现在再来看常见的面试或笔试题就很简单了:

cpp 复制代码
Q:下列程序的输出结果:
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2);
A:true,均指向常量池中对象。

Q:下列程序的输出结果:
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2);
A:false,两个引用指向堆中的不同对象。

Q:下列程序的输出结果:
String s1 = "abc";
String s2 = "a";
String s3 = "bc";
String s4 = s2 + s3;
System.out.println(s1 == s4);
A:false,因为s2+s3实际上是使用StringBuilder.append来完成,会生成不同的对象。

Q:下列程序的输出结果:
String s1 = "abc";
final String s2 = "a";
final String s3 = "bc";
String s4 = s2 + s3;
System.out.println(s1 == s4);
A:true,因为final变量在编译后会直接替换成对应的值,所以实际上等于s4="a"+"bc",而这种情况下,编译器会直接合并为s4="abc",所以最终s1==s4。

Q:下列程序的输出结果:
String s = new String("abc");
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s == s1.intern());
System.out.println(s == s2.intern());
System.out.println(s1 == s2.intern());
A:false,false,true。

Q:下列程序的输出结果:
  String a=new String("abc");
  String b="abc";
  System.out.println(a==b); 
  String a1=new String("abcd").intern();
  String b1="abcd";
  System.out.println(a1==b1); 
A:false,true


Q:下列程序的输出结果:
     String a = new String("ab") + new String("c");
     String b = "abc";
     System.out.println(a == b); 
     String a1 = new String("ab") + new String("cd");
     a1.intern();
     String b1 = "abcd";
     System.out.println(a1 == b1); 
A:false,true

五:总结

对于程序中大量使用存在的字符串时,尤其存在很多已经重复的字符串时,使用intern()方法能够节省内存空间。

大的网站平台,需要内存中存储大量的字符串。比如社交网站,很多人都存储:北京市、海淀区等信息。这时候如果字符串都调用intern()方法,就会很明显降低内存的大小。

以下是java在不同版本中String字符串的内存变化,希望可以对你有所帮助。

相关推荐
陈小桔1 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!1 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
xiaogg36781 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
逆光的July1 小时前
Hikari连接池
java
微风粼粼1 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad1 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud
天若有情6732 小时前
Spring MVC文件上传与下载全面详解:从原理到实战
java·spring·mvc·springmvc·javaee·multipart
祈祷苍天赐我java之术2 小时前
Redis 数据类型与使用场景
java·开发语言·前端·redis·分布式·spring·bootstrap
Olrookie3 小时前
若依前后端分离版学习笔记(二十)——实现滑块验证码(vue3)
java·前端·笔记·后端·学习·vue·ruoyi
倚栏听风雨4 小时前
java.lang.SecurityException异常
java