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字符串的内存变化,希望可以对你有所帮助。

相关推荐
码上一元2 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
计算机-秋大田2 小时前
基于微信小程序的养老院管理系统的设计与实现,LW+源码+讲解
java·spring boot·微信小程序·小程序·vue
魔道不误砍柴功4 小时前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot
失落的香蕉4 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
枫叶_v4 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
wclass-zhengge4 小时前
SpringCloud篇(配置中心 - Nacos)
java·spring·spring cloud
路在脚下@4 小时前
Springboot 的Servlet Web 应用、响应式 Web 应用(Reactive)以及非 Web 应用(None)的特点和适用场景
java·spring boot·servlet
黑马师兄4 小时前
SpringBoot
java·spring
数据小小爬虫4 小时前
如何用Java爬虫“偷窥”淘宝商品类目API的返回值
java·爬虫·php
暮春二十四4 小时前
关于用postman调用接口成功但是使用Java代码调用却失败的问题
java·测试工具·postman