吃透String的intern方法

三种常量池

1、常量池版本变化:

  • jdk1.6及以前,运行时常量池在方法区中
  • jdk1.7,运行时常量池移到堆中去了
  • jdk1.8,运行时常量池移到元空间中,取消了方法区(永久代)

2、下面是jdk1.8的三种常量池

类文件中的常量池:

存在class文件中,所处区域堆中,内容概要符号引用和字面量,class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用。

运行时常量池:

存在于内层元空间中,JVM运行时,类信息、常量、静态变量、即时编译期编译后的代码

class类文件中的常量池(编译器生成的各种字面量和符号引用)会在类加载后被放入这个区域

运行时常量池是在类加载完成之后,将每个class类常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致

字符串常量池:

  • jdk1.7之前存在方法区中。(jdk1.8方法区直接没了,取而代之的是元空间)
  • jdk1.7/8及后面,存在堆中,下面intern研究的就是该区

可以使用代码验证(jdk1.7以前版本这里就不演示了,ps:OutOfMemoryError: PermGenspace)

下面是jdk1.8,反复创建字符串会oom,堆内存溢出,证明字符串常量词是在堆中

3、各种字面量

对于String s="2",int i=3;  2、3这两个值就是字面量

4、符号引用

当一个A类引用了B类的时候,A类加载编译的时候,不知道B类是否被加载,在内存等,就在A类中符号引用记录B类,当A运行时,当虚拟机将B加载到实际内存的时候,就将B类的符号引用替换成实际地址(每一个类在编译的时候都会存在大量符号引用,以供虚拟机在具体执行类的时候将符号地址替换成实际地址)

ps:文章后面提到如果没有写明都是指字符串常量词 

补充知识

  1. 有一张表string pool维护着字符串常量词的引用。
  2. 对于字符串创建,不管是直接创建还是new,只要是遇到""引号就会去常量池检查引号里面的字符,没有则创建。
  3. jdk1.7以前,intern方法在调用后会在常量池找,如果没有找到会将String插入到常量池中,如果找到,则返回String常量池的引用(此时常量词在方法区,不在堆中)。
  4. jdk1.7及其后,intern方法在调用后首先是在string pool中的常量池找,没有则在堆里找,找到则返回堆中引用到常量池,在返回常量池的引用,实际上是间接返回堆中的引用。都没有找到则在字符串常量池创建一个,并返回。(重点)

字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的)。 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(也就是我们常说的用双引号括起来的)的引用(而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。

看下面例子前,了解两个创建对象语句(这里假设常量词是没有字符1)

1、String s = new String("1");

对于这个语句,它创建了两个对象,一个是new 一个是字符串常量池中的对象(遇到引号,先在字符串常量池创建"1",然后在堆中new对象

2、String s = new String("1") + new String("1");

对于这个语句,它共创建了五个对象

  1. 两个new匿名对象(堆中)

  2. 一个1的字符串常量(new String("1")的时候,就创建了1的字符串常量,第二个new因为已经存在,所以不再创建)

  3. 一个其内部实现是先new一个StringBuilder对象,然后 append(new String("1")),append(new String("1")

  4. 最后一个让s引用toString()返回的对象“11”,相当于new出来的一个对象,但是“11”并没有加入字符串常量池。

例子讲解

下面都是在jdk1.8的情况下

1、未使用intern

String s = new String("1");
String s2 = "1";
System.out.println(s == s2);//false

输出false

其中s是引用了new的对象,但是它还在字符串常量池创建了对象。s2引用的是字符串常量池的对象,因为s已经创建好了。两个引用不一样,所以输出false。

2、使用了intern

String s = new String("1").intern();
String s2 = "1";
System.out.println(s == s2);//true

输出true

其中s是引用了new的对象,但是它还在字符串常量池创建了对象,并且调用intern后,返回了字符串常量池的引用,最后s实际是引用了字符串常量池的引用。s2引用的是字符串常量池的对象,因为s已经创建好了。两个引用都是字符串常量池的引用,所以输出true。

3、使用了intern

String s = new String("1");
String s2 = s.intern();
String s3 = "1";
System.out.println(s == s2);//false
System.out.println(s == s3);//false
System.out.println(s2 == s3);//true

输出false false true

其中s是引用了new的对象,但是它还在字符串常量池创建了对象。s2对象调用了intern后,字符串常量池已经有"1",返回了"1"字符串常量池的引用。s3引用的是字符串常量池的对象,因为s已经创建好了。s2与s3引用都是字符串常量池的引用,所以输出true。s引用的是new的引用,与它比较输出false

4、未使用intern

String s3 = new String("1") + new String("1");
String s4 = "11";
System.out.println(s3 == s4);//false

输出false

引号内容只有1,字符串常量池只创建了"1",没有创建"11",s3返回的是引用了toString产生的对象,并且它不在字符串常量池。s4未在字符串常量池找到"11",所以在字符串常量池创建了"11"并返回引用。最终两者对象引用不一样,输出false。

5、使用了intern(常量池在jdk1.7移到了堆中)

String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);//true

输出true

其中s3是引用了toString产生的对象,相当于new在堆中的对象s3,并且它不在字符串常量池。调用s3.intren()后,在字符串常量池引用了new的对象s3的引用,s4在字符串常量池找到"11"并直接返回其引用,其实就是间接引用了在堆中的对象s3。最终两者对象引用一样,输出true。(多读几遍

如下图看:

是不是认为自己懂了?那就测试一下吧

package com.xue.test;

/**
 * 彻底吃透String--常量池--intern
 * @author 
 *
 */
public class Test3 {
	public static void main(String[] args) {
		//一次放开一个多行注释运行
	        
	       /* String s = new String("1");
	        s.intern();
	        String s2 = "1";
	        System.out.println(s == s2);//false
	        String s3 = new String("1") + new String("1");
	        s3.intern();
	        String s4 = "11";
	        System.out.println(s3 == s4);//true
	        */	        
	        
	        /*String s = new String("1");
	        String s2 = "1";
	        s.intern();
	        System.out.println(s == s2);//false
	        String s3 = new String("1") + new String("1");
	        String s4 = "11";
	        s3.intern();
	        System.out.println(s3 == s4);//false
	         */	 
		
			
	 		/*//+连接但编译器不优化
	        String s1 = new String("xy") + "z";  
	        String s2 = s1.intern();  
	        System.out.println( s1 == s1.intern() );//true  
	        System.out.println( s1 + " " + s2 );  
	        System.out.println( s2 == s1.intern() );//true 
	 		 */
		
	      	/*// 一般情况
	        String s1 = new String("xyz") ;  
	        String s2 = s1.intern();  
	        System.out.println( s1 == s1.intern() );//false  
	        System.out.println( s1 + " " + s2 );  
	        System.out.println( s2 == s1.intern() );//true 
	      	 */	        

	        /*//编译器优化,会拼成 "xyz"在编译
	        String s1 = "xy" + "z";
	        String s2 = s1.intern();
	        System.out.println( s1 == s1.intern() );//true  
	        System.out.println( s1 + " " + s2 );  
	        System.out.println( s2 == s1.intern() );//true 
	         */
	        
	}

}

还不懂?

看参考博客:String的Intern方法详解

参考博客:String放入运行时常量池的时机与String.intern()方法解惑

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值