Sting和intern()全面解析

String不可变说明

final说明

  1. 修饰类,标识该类不能被继承,该类的所有方法自动成为final方法
  2. 修饰方法,方法不能被重写
  3. 修饰基本数据类型,表示为常量,值不能修改
  4. 修饰引用类型变量,标识引用地址不可变,但是引用地址对象的属性可以改变

String部分源码

public final class String
    implements java.io.Serializable, Comparable, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

String类被final修饰,不能被重写,value被final修饰,不可变。并且没有setter方法,保证String不可变。

可以通过反射拿到value对象,进行改变。

String设置不可变的原因

  1. 和运行时常量池字符串创建过程有关:String s = "abc"类似这种赋值时,先查看字符串常量池中是否有abc,如果有,直接将s指向abc,如果没有则新建一个abc,将s指向Sting类中的hash字段,因为不可变,hashcode唯一,可以使用hash进行缓存,不用每次都计算。
  2. 安全性

identityHashCode()返回对象物理地址的hashcode;hashCode()返回重写后的hashCode。如果没重写两个一样。

String 内存分配

  1. 对于直接双引号创建的字符串,jdk1.6以前放在永久代中,jdk1.7以后放在堆中。原因:永久代较小,放太多的话会OOM,另外永久代垃圾回收效率低,频率低,但String使用较多。
  2. 对于String s2=new String("abc)创建的,如果常量池中存在"abc",则先会在堆中 创建s2对象,然后将s2的地址指向常量池中存在的常量;如果常量池中本身没有"abc",在堆中创建一个String对象,并赋值给s2;当调用s2=s2.intern()之后,会将s2指向常量池(见下文分析)。

StringTable

字符串常量池中是不会存储相同字符串的。·String底层存储是一个固定大小的HashTable

new String()创建了几个对象?

2个,一个对象,一个字符串常量池的,一个堆空间的
toString()不会再字符串常量池中创建。
String s1 = new String("a") + new String("b");字符串常量池中并不会有ab。

String 字符串拼接原理

        String str1 = "a";
        String str2 = new String("b");
        String str3=str1+str2;
        //创建一个StringBuilder
        StringBuilder sb = new StringBuilder();
        sb.append(str1);
        sb.append(str2);
        //toString返回return new String(value, 0, count);
        str3 = sb.toString();

对于String str3=str1+str2;其实相当于建了一个StringBuilder对象进行拼接,最后再调用sb.toString()方法返回一个新的String对象。因此使用加号拼接的话会创建多个StringBuilder对象和String对象。

如果两个都是常量或者常量引用,则会编译期优化。

intern()方法

JDK1.6中:从字符串常量池中查询是否存在该字符串,如果存在,直接返回常量池字符串地址,不存在则在常量池中创建。并返回常量池地址。
JDK1.8中:从字符串常量池中查询是否存在该字符串,如果存在,直接返回常量池字符串地址,如果不存在,则会在常量池中创建,并把对象地址引用复制一份,放入常量池中,返回对象地址引用。(原因是1.8中字符串常量池在堆中,避免重复创建,浪费空间)

1、如果字符串常量池中本身存在进行intern();

        public static void main(String[] args) {
        String a1="abc";
        String a2= new String("abc");
        System.out.println(a1==a2);//false,一个指向对象,一个指向常量池
        a2=a2.intern();
        System.out.println(a1==a2);//true,2个都指向常量池
    }
常量池中存在,执行intern()方法

下面演示常量池中没有的情况下进行intern();

        String s1 = new String("a") + new String("b");
        String s2=s1.intern();
        System.out.println(s1 =="ab");//输出true
        System.out.println(s2 =="ab");//输出true
指向intern()方法是常量池中没有ab的情况

3、intern()之后不赋值情况

        String s1 = new String("a") + new String("b");
        s1.intern();
        String s2="abc";
        System.out.println(s1 =="ab");//输出true

疑问: String s2="abc";到底s2是啥?常量池对象的地址还是堆中字符串对象的的引用?

解答:
常量池:字面量和符号引用
运行时常量池:方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池(Constant Pool Table),存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池。
全局字符串常量池
HotSpot VM里,记录了一个全局表叫做StringTable,它本质上就是个HashSet。这是个纯运行时的结构,而且是惰性(lazy)维护的。注意它只存储对java.lang.String实例的引用。

一般我们说一个字符串进入了全局的字符串常量池其实是说在这个StringTable中保存了对它的引用,反之,如果说没有在其中就是说StringTable中没有对它的引用。

所以常量池中放的并不是真正的String对象,而是对象的引用。String s2="abc"。s2返回的时堆中abc对象的引用。

你可能感兴趣的:(Sting和intern()全面解析)