Bootstrap

Java面试 : String

串池: StringTable,可以理解为一个对象数组 [ "a" ,"b","ab"] 每一个元素都是一个字符串对象

1. 常量池与串池的关系

String s1="a";
String s2="b";
String s3="ab";

上述代码的运行过程:

常量池中的信息会被加载到运行时常量池中,这时 a b ab 都是常量池中的符号,还没有变成Java字符串对象

当编译过后运行到对应的行数 会执行

  • ldc #2 将 a 符号变成 “a” 字符串对象

  • ldc #3 将 b 符号变成 “b” 字符串对象

  • ldc #4 将 ab 符号变成 “ab” 字符串对象

执行第一句后会去串池中比较,如果找到了,就把串池中的value赋给这个String对象,如果找不到信息,就把 “a” 字符串对象放进池中

附加:StringTable是一个hashtable结构,不能扩容

2. 字符串拼接

String s4=s1+s2;

编译之后在jvm中的拼接过程

new String = new StringBuilder().append(s1).append(s2).toString 
  • 面试题:以上述条件为基础 s4==s3 的结果是什么

    • 答案:false

    • 原因:s3声明并赋值后存储在串池中,而从s4的运行结果来看最后调用了toString方法,查看toString方法,会发现它使用new关键字创建了新的String对象,是存储在堆内存中的,他们两个并不是一个对象,这也是证明了字符串的比较不能使用 == 而要使用equals方法,

    • 详细内容可以参考本人的另一篇文章hashcode重写必要性:直观方式理解重写hashcode的必要性-CSDN博客

  • 理解性延展

    String s1="a";
    String s2="a";

    这里s1==s2的结果为true,因为这里操作的是串池,再次声明 "a"对象会直接去串池寻找,找到了直接返回,是同一个字符串对象

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

    这里s3==s4的结果为false,依照上面的内容,s3与s4都会分别new一个新的String对象,在堆内存指向两个不同的对象,只是他们作为字符串展示出来的结果一致而已

3. 编译期优化

String s1="ab";
String s2="a"+"b";
System.out.println(s1==s2)

上述代码运行结果为true

Javac在编译期的优化,由于"a"和"b"都是常量,结果在编译器就已经确定为"ab" 但是上一个字符串拼接的内容是变量相加,所以不会优化

4. intern方法

//a存放在串池中
String a="ab"
//堆中分别new了两个对象 "a" "b" 拼接后返回一个String对象 "ab" 所以变量b是存储在堆中的
String b=new String("a")+new String("b")
//尝试将b放入串池,如果有则不放入,直接返回串池中的对象,这里放入失败,b仍然在堆中,返回的是串池中的对象,本质上和a是一个对象
String s= b.intern();
    System.out.println(a==b);
    System.out.println(b==s);

上述代码的结果为 false true

过程分析:①串池中放入一个 "ab" 对象 ,②堆中new了一个由 "a" "b" 拼接的对象 ③尝试将b放入串池,发现串池中已经存在"ab"对象,直接返回给s

误区分析:变量b 如果没有inern成功的话,仅仅是一个存放在堆中的对象,如果调用intern方法之后,发现串池中没有这个字符串对象,那么就会把 b 所引用的堆中的对象放入串池然后返回,所以没有a变量的情况下 b是存放在堆中的,如果有a变量,则b从堆中移动到串池中

扩展:jdk1.6之前 如上代码调用intern的时候,是把b复制一份放入串池,也就是说无论如何b都是一个存放在堆中的对象

;