深入Java字符串常量池与Java字符串的创建与存储机制

看了好多篇博客,大家对字符串常量池的理解各有所见,自己对此也进行了探究,并将自己的理解加验证进行了整理,希望能和大佬们共同讨论。

一、字符串常量池的位置

这个比较确定,Jdk1.6及以前是存在与方法区(永久代)中,而Jdk1.7之后(本人现在用的1.8.0_192)字符串常量池被移到了堆内存中。

二、字符串的存储位置

字符串创建过程不同对应的字符串的存储位置也不同,主要有两种没有异议:

1.直接将字符串常量赋给字符串变量

String str="abcd";

对于这种语句,将在字符串常量池中创建一个"abcd"字符串对象,并将此对象的应用返回个str,并且如果字符串常量池中存在此字符串对象(用equals(oject)方法确定),直接返回该对象的应用。

2.使用new语句来创建字符串

String str=new String("abcd");

对于这种语句,会先判断"abcd"是否已经存在于字符串常量中(用equals(oject)方法确定),若存在,则在堆内存中创建一个"abcd"对象,并返回其对象的应用;若不存在,则先在字符串常量池中创建一个"abcd"字符串对象,之后再在堆内存中创建一个"abcd"对象,并返回堆内存对象的应用。

接下来来看一下String类的构造:

public final class String {

    private final char value[];//String类底层对字符串的封装为不可变字符串数组


    private int hash; // Default to 0
    
    public String(String original) {//String类的构造函数之一
        this.value = original.value;
        this.hash = original.hash;
    }

}

由上段代码可以看出,在new String("abcd")的时候会对创建在堆内的字符串对象进行初始化,且其初始化只是将字符串常量池中字符串的底层实现的应用赋值给了堆内存的字符串对象的底层实现,换句话说,字符串常量池中字符串的底层实现和堆内存中字符串的底层实现指向同一个字符串数组。其结构如下图所示:

下面在假定前边两种情况没有问题的基础上讨论其他的情况:

3.字符串的+运算

  • String str="ab"+"cd";

因为"ab"、"cd"都是常量,程序在编译时会自动把他们优化为String str="abcd";故而这个操作和1操作相同。

对编译完生成的字节码使用javap进行反编译可以看出程序在编译时做出的优化。

例如:程序如下

public class Test {

	String s1="a";
	String s2="b";
	String s3="a"+"b";
	String s4=s1+s2;

}

编译完反编译的结果如下:

public class test.Test {
  java.lang.String s1;

  java.lang.String s2;

  java.lang.String s3;

  java.lang.String s4;

  public test.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: ldc           #2                  // String a
       7: putfield      #3                  // Field s1:Ljava/lang/String;
      10: aload_0
      11: ldc           #4                  // String b
      13: putfield      #5                  // Field s2:Ljava/lang/String;
      16: aload_0
      17: ldc           #6                  // String ab
      19: putfield      #7                  // Field s3:Ljava/lang/String;
      22: aload_0
      23: aload_0
      24: getfield      #3                  // Field s1:Ljava/lang/String;
      27: aload_0
      28: getfield      #5                  // Field s2:Ljava/lang/String;
      31: invokedynamic #8,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
      36: putfield      #9                  // Field s4:Ljava/lang/String;
      39: return
}

由17,19行可以看出,程序直接给s3赋值为"ab"。而s4则是在程序运行时动态的赋值。

  • String str=new String("ab")+new String("cd");

结论:相当于执行两次2上的操作,因此会在常量池里创建"ab"、"cd"字符串对象(若不存在)。而在堆中会创建"ab"、"cd"对象和"abcd"对象(由于"abcd"对象是在程序执行过程中产生的,并且String类为不可变类,故而只会在堆中创建此对象)。

  • String str=new String("ab")+"cd";

结论:在堆内存创建一个ab对象,abcd对象,在常量池创建一个cd对象(若不存在),将堆内存的abcd对象的引用返回给变量str。

  • String s1="ab";String s2="cd";String s3=s1+s2;

结论:对于s3来说,只在堆内存创建abcd对象,并将对象的引用返回个s3。

  • String s1="ab";String s2=s1+"cd";

结论:对于s2来说,在堆内存创建abcd对象,并在常量池创建cd对象(若不存在),并将堆内存的abcd对象的引用返回给s2。

  • final String s="ab";String s2=s+"cd";

结论:final修饰的s可以看作常量,s2等同于String s2="ab"+"cd";

以上结论的证明均可采用String类的intern()方法。接下来介绍intern()方法。

三、intern()方法

String.intern()是一个Native方法,它的作用是:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对向的引用。

看一个代码:

public class Test{

    public static void main(String[] args){

		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);

    }
}

这段代码在JDK1.6中运行,会得到两个false,而在JDK1.7中运行,会得到一个true和一个false。产生差异的原因:

  • 在JDK1.6中,intern()方法会把首次遇到的字符串实例复制到常量池中,返回的也是常量池中这个字符串实例的引用,而由StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用。
  • 而JDK1.7的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例的引用,因此intern()返回的引用和由StringBuilder创建的哪个字符串实例可以是同一个。

本文用的实验环境为JDK1.8,故而由上边程序得到的第二个false结果可推断出"java"这个字符串在str2.intern()之前已经存在于字符串常量池中了。因此由intern()的这个特性可以用来证明二中的部分结论,举例如下:

public class Test {

	public static void main(String[] args) {
		String s1="a";
//		String s5=s1+"cd";
		String s6=new String("c")+new String("d");
		System.out.println(s6.intern()==s6);
	}

}

如果注释掉s5行,输出结果为true,如果将s5行去掉注释,输出结果为false。因此,证明了对于String s5=s1+"cd"语句,会在常量池中创建cd对象。

 

 

参考

彻底搞懂Java 字符串常量池和intern方法

深入理解Java虚拟机

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值