字符串常量池

常量池有三种:class文件常量池、运行时常量池、字符串常量池

class文件常量池(constant_pool)

class文件中的一部分,是一个静态的存储结构,常量池中两大类常量

  1. 字面量:文本字符串(类中所有用双引号引起来的字符串字面量)
  2. 符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符
运行时常量池(runtime constant pool)

class文件常量池的运行时表示形式

  1. 一直在方法区中,HotSpot JDK1.8中,方法区的实现为元空间(Metaspace),存在于本地内存中
全局字符串常量池

HotSpot VM里,记录interned string的一个全局表叫做StringTable(猜测:类似HashMap<hashCode, reference>,HashSet底层也是HashMap)

1. 初始为空,由String类维护

A pool of strings, initially empty, is maintained privately by the class String.——jdk8 String.intern()注释

2. 全局的(运行时常量池是一个类对应一个)
public class StringTest {

    public static void main(String[] args) {
        System.out.println(new String1().a == new String2().b);//true
    }
}
public class String1 {

    String a = "123";
}
public class String2 {

    String b = "123";
}
3. 字面量相加(+),编译时就直接拼接
// 源码:
public class Test {

    public static void main(String[] args) {

        String str = "aa" + "bb" + "cc";
    }
}
// javap -v Test
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#13         // java/lang/Object."<init>":()V
   #2 = String             #14            // aabbcc
   #3 = Class              #15            // Test
   #4 = Class              #16            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               SourceFile
  #12 = Utf8               Test.java
  #13 = NameAndType        #5:#6          // "<init>":()V
  #14 = Utf8               aabbcc
  #15 = Utf8               Test
  #16 = Utf8               java/lang/Object
{
  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // String aabbcc
         2: astore_1
         3: return
      LineNumberTable:
        line 6: 0
        line 7: 3
}
SourceFile: "Test.java"

从反编译的class文件中可以看到,只存在“aabbcc”

4. 只要存在new String(…)相加(+),则使用StringBuilder的append()和toString()方法,toString()实现为new String(…)
public class Test {

    public static void main(String[] args) {

        String str1 = new String("aa") + new String("bb");
		String str2 = "bb" + new String("cc");
    }
}
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #12.#21        // java/lang/Object."<init>":()V
   #2 = Class              #22            // java/lang/StringBuilder
   #3 = Methodref          #2.#21         // java/lang/StringBuilder."<init>":()V
   #4 = Class              #23            // java/lang/String
   #5 = String             #24            // aa
   #6 = Methodref          #4.#25         // java/lang/String."<init>":(Ljava/lang/String;)V
   #7 = Methodref          #2.#26         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = String             #27            // bb
   #9 = Methodref          #2.#28         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #10 = String             #29            // cc
  #11 = Class              #30            // Test
  #12 = Class              #31            // java/lang/Object
  #13 = Utf8               <init>
  #14 = Utf8               ()V
  #15 = Utf8               Code
  #16 = Utf8               LineNumberTable
  #17 = Utf8               main
  #18 = Utf8               ([Ljava/lang/String;)V
  #19 = Utf8               SourceFile
  #20 = Utf8               Test.java
  #21 = NameAndType        #13:#14        // "<init>":()V
  #22 = Utf8               java/lang/StringBuilder
  #23 = Utf8               java/lang/String
  #24 = Utf8               aa
  #25 = NameAndType        #13:#32        // "<init>":(Ljava/lang/String;)V
  #26 = NameAndType        #33:#34        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #27 = Utf8               bb
  #28 = NameAndType        #35:#36        // toString:()Ljava/lang/String;
  #29 = Utf8               cc
  #30 = Utf8               Test
  #31 = Utf8               java/lang/Object
  #32 = Utf8               (Ljava/lang/String;)V
  #33 = Utf8               append
  #34 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #35 = Utf8               toString
  #36 = Utf8               ()Ljava/lang/String;
{
  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=3, args_size=1
         0: new           #2                  // class java/lang/StringBuilder
         3: dup
         4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
         7: new           #4                  // class java/lang/String
        10: dup
        11: ldc           #5                  // String aa
        13: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: new           #4                  // class java/lang/String
        22: dup
        23: ldc           #8                  // String bb
        25: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        31: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        34: astore_1
        35: new           #2                  // class java/lang/StringBuilder
        38: dup
        39: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
        42: ldc           #8                  // String bb
        44: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        47: new           #4                  // class java/lang/String
        50: dup
        51: ldc           #10                 // String cc
        53: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        56: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        59: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        62: astore_2
        63: return
      LineNumberTable:
        line 5: 0
        line 6: 35
        line 7: 63
}
SourceFile: "Test.java"
  1. 对于String str1 = new String("aa") + new String("bb");,字节码指令为0~34,0先new StringBuilder对象,28再调用appender方法,31最后调用toString()创建String对象
  2. 对于String str2 = "bb" + new String("cc");,字节码指令为35~62,32 new StringBuilder对象,44 append 字符串“bb”,56 append字符串“cc”,59 toString创建String对象
5. 运行时生产的字符串不会进入字符串常量池,除非调用String.intern()方法
public class StringTest {

    public static void main(String[] args) {
        String str1 = new StringBuilder("abc").append("def").toString();
        System.out.println(str1.intern() == str1); //true

        // 下面的代码用于对比测试
        String str2 = new StringBuilder("defdef").toString();
        System.out.println(str2.intern() == str2); //false
        
        // 下面的代码用于对比测试(特殊字符串)
        String str3 = new StringBuilder("ja").append("va").toString();
        System.out.println(str3.intern() == str3); //false
    }
}
  1. 第一行代码,解析时,在堆中new了三个对象,“abc”、“def"和"abcdef”,并把"abc"、"def"引用放入字符串常量池,但字符串"abcdef"是运行时产生的,jvm并不会把它放入池中,调用intern()方法时,jvm把str1这个引用放入池中,所以str1.intern() == str1为true
  2. 第三行代码,堆中new了两个"defdef"对象,一个地址赋值给str2,一个地址存入池中,调用intern()方法时,是把池中的地址返回,故str2.intern() == str2为false
  3. 第五行代码,是由于"java"字符串在jvm启动加载的类中使用到,已经在池中了
6. 这是个纯运行时的结构,而且是惰性(lazy)维护的

这个惰性是由于类加载时的解析阶段造成的,HotSpot VM在遇到操作符号引用的字节码指令(如ldc)时,会先对指令操作的对象进行解析,把它从符号引用解析为直接引用

备注:类字段在类初始化时,会进行解析

7. 所有具有相同字符序列的字符串对象,引用的都是同一个字符序列
public class StringTest {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        String str1 = new String("aaa");
        String str2 = new String("aaa");
        String str3 = "aaa";
        Field field = String.class.getDeclaredField("value");
        field.setAccessible(true);
        char[] value = (char[]) field.get(str1);
        value[2] = 'c';
        System.out.println(str1);//aac
        System.out.println(str2);//aac
        System.out.println(str3);//aac
    }
}

也就是说,对象里的字段引用的是同一个地址private final char value[]

8. 各版本String.intern()的区别
public class StringTest {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        String str = new StringBuilder("计算机").append("软件").toString();
        System.out.println(str.intern() == str);//jdk<=1.6为false,jdk>=1.7为true
    }
}
  1. jdk6及以前,将堆中str对象复制一份到方法区的字符串常量池中
  2. jdk7及以后,将str引用存入堆中字符串常量池中,不会再创建对象
9. 常见面试题

String str = new String("aaa")创建几个对象?

首先,创建"aaa"对象,并把引用存入字符串常量池,然后,创建str对象,所以池中的引用并不是str

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值