深入理解Java String

2020/03/12 Java 共 2673 字,约 8 分钟

解释Java String的不可变性以及字符串常量池。

不可变性

  • String的数据保存在内部不可变的字符数组之中,private final char value[]
    • 私有访问权限保证了只有String内部方法才能访问数据
    • final保证了value引用的地址不可变
  • String类被final修饰,不可继承,避免内部数据被其他类继承从而遭到破坏
  • String类的所有方法都不会改变value的值

这些特点导致了String封装的字符串数据是不可改变的。

字符串比较

全局字符串池

字符串的分配,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能,JVM为了提高性能和减少内存开销,开辟了一个特殊的空间,全局字符串池,JDK1.6字符串池在方法区中,JDK1.7在堆中

字符串池中保留堆中字符串常量的对象引用。在HotSpot VM里实现字符串池功能的是一个 StringTable类,它是一个哈希表,里面存的是驻留字符串(字面量)的引用。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。JDK1.6字符串池中直接保存字符串实例,而JDK1.7保存对象引用

字符串创建

  • 使用new关键字创建字符串,String s1 = new String("abc");
    • 由于指定了字面量”abc”,首先在字符串池中寻找”abc”的引用,若有则返回引用,否则在堆中创建对象”abc”(1),并在字符串中保留对象引用
    • 使用了new关键字,在堆中创建字符串对象”abc”(2)
    • 此处”abc”(1)与”abc”(2)都是堆中的字符串对象,但是不是同一个
  • 使用字面量创建字符串,String s2 = "abc";
    • 首先在字符串池中寻找”abc”的引用,若有则返回引用,否则在堆中创建对象”abc”,并在字符串中保留对象引用
    • s2保存”abc”的对象引用,不会在堆上创建新的对象
  • s.intern()
    • 在字符串池中寻找字符串对象的引用,若有则返回引用,否则在字符串池中保存s的对象引用

字符串串联

  • 如果含有String对象,则底层是使用StringBuilder实现的拼接的,所以会在堆中创建一个新实例
    String str1 ="str1";
    String str2 ="str2";
    String str3 = str1 + str2;
    
  • 如果只有字面量或者常量或基础类型变量,则会直接编译为拼接后的字符串字面量参数不会保存在常量池中
    // 等同于String str1 = "aabb"
    String str1 = "aa" + "bb";
    

字面量何时进入常量池

  • 对于HotSpot VM,加载类的时候,字符串字面量会进入到当前类的runtime常量池,不会进入全局的字符串常量池
  • 在字面量赋值的时候,会翻译成字节码ldc指令,ldc指令触发lazy resolution动作到当前类的runtime常量池去查找该index对应的项
  • 如果该项尚未resolve则resolve之,并返回resolve后的内容。
  • 在遇到String类型常量时,resolve的过程如果发现StringTable已经有了内容匹配的java.lang.String的引用,则直接返回这个引用;
  • 如果StringTable里尚未有内容匹配的String实例的引用,则会在Java堆里创建一个对应内容的String对象,然后在StringTable记录下这个引用,并返回这个引用出去。

实例

String s = new String("2");
s.intern();
String s2 = "2";
// JDK1.6:false
// JDK1.7:false
System.out.println(s == s2);

String s3 = new String("3") + new String("3");
s3.intern();
String s4 = "33";
// JDK1.6:false
// JDK1.7:true
System.out.println(s3 == s4);

JDK1.6

  • String s = new String("2");创建了两个对象,一个在堆中的String对象(s指向),一个是在常量池中的”2”对象。
  • s.intern();在常量池中寻找与s变量内容相同的对象,发现已经存在内容相同对象”2”,返回对象”2”的地址。
  • String s2 = "2";使用字面量创建,在常量池寻找是否有相同内容的对象,发现有,返回对象”2”的地址
  • ss2,一个是堆中的String对象,一个是常量池中的”2”对象,不一样。

  • String s3 = new String("3") + new String("3");创建了两个对象,一个在堆中的String对象(s3指向),一个是在常量池中的”3”对象。不讨论匿名对象。
  • s3.intern();在常量池中寻找与s3变量内容相同的对象,没有发现”33”对象,在常量池中创建”33”对象,返回”33”对象的地址
  • String s4 = "33";使用字面量创建,在常量池寻找是否有相同内容的对象,发现有,返回对象”33”的地址
  • s3s4,一个是堆中的String对象,一个是常量池中的”33”对象,不一样。

JDK1.7

  • String s = new String("2");创建了两个对象,一个在堆中的String对象(s指向),一个是在堆中的”2”对象,并在常量池中保存”2”对象的引用地址。
  • s.intern();在常量池中寻找与s变量内容相同的对象,发现已经存在内容相同对象”2”,返回对象”2”的地址。
  • String s2 = "2";使用字面量创建,在常量池寻找是否有相同内容的对象,发现有,返回对象”2”的地址
  • ss2,一个是堆中的String对象,一个是堆中”2”对象(常量池中存在其对象引用),不一样。

  • String s3 = new String("3") + new String("3");创建了两个对象,一个在堆中的String对象(s3指向),一个是在堆中的”3”对象,并在常量池中保存”3”对象的引用地址。不讨论匿名对象。
  • s3.intern();在常量池中寻找与s3变量内容相同的对象,没有发现”33”对象,将s3指向的String对象的地址保存到常量池中,返回String对象的地址
  • String s4 = "33";使用字面量创建,在常量池寻找是否有相同内容的对象,发现有,返回常量池中String对象的地址
  • s3s4,都指向堆中的String对象。

REFERENCE

[1] Java中几种常量池的区分
[2] java基础:String — 字符串常量池与intern(二)

文档信息

Search

    Table of Contents