Bootstrap

关于String,StringBuilder,StringBuffer,字符串常量池

String是不变得,源码分析

https://zhuanlan.zhihu.com/p/38144507

如何查看源码

https://blog.csdn.net/luo_da/article/details/73744544

java.lang.String、StringBuilder、StringBuffer 源码解析

https://blog.csdn.net/u011080472/article/details/51387086

Java 中new String(“字面量”) 中 “字面量” 是何时进入字符串常量池的?

https://www.zhihu.com/question/55994121/answer/147261751
木女孩的回答~(需要总结)

都有哪些常量池?

1.Class文件中的常量池

  • 这里面主要存放两大类常量: 字面量(Literal):文本字符串等
  • 符号引用(Symbolic References):属于编译原理方面的概念,包含三类常量: 类和接口的全限定名(Full Qualified Name) ,字段的名称和描述符(Descriptor)
  • 方法的名称和描述符这个用javap看一下就能明白,这里只涉及字符串就不谈其他的了。简单地说,用双引号引起来的字符串字面量都会进这里面。

2.运行时常量池

  • 方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池(Constant Pool Table),存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池。

3.全局字符串常量池

  • HotSpot VM里,记录interned string的一个全局表叫做StringTable,它本质上就是个HashSet<String>。这是个纯运行时的结构,而且是惰性(lazy)维护的。注意它只存储对java.lang.String实例的引用,而不存储String对象的内容。 注意,它只存了引用,根据这个引用可以得到具体的String对象。
  • 一般我们说一个字符串进入了全局的字符串常量池其实是说在这个StringTable中保存了对它的引用,反之,如果说没有在其中就是说StringTable中没有对它的引用。

String的 intern 方法干了什么?

JDK7中,如果常量池中已经有了这个字符串,那么直接返回常量池中它的引用,如果没有,那就将它的引用保存一份到字符串常量池,然后直接返回这个引用。敲黑板,这个方法是有返回值的,是返回引用。

字面量进入字符串常量池的时机

看到这里想必也就明白了(关于Resolve和LDC不是很理解导致不理解木女孩的解释,但是只看这个结论也可以接受), 就HotSpot VM的实现来说,加载类的时候,那些字符串字面量会进入到当前类的运行时常量池,不会进入全局的字符串常量池(即在StringTable中并没有相应的引用,在堆中也没有对应的对象产生)

对于代码

class NewTest1{
    public static String s1="static";  // 第一句
    public static void main(String[] args) {
        String s1=new String("he")+new String("llo"); //第二句
        s1.intern();   //第三句
        String s2="hello";  //第四句
        System.out.println(s1==s2);//输出是true。
    }
}
  • “static” “he” “llo” “hello”都会进入Class的常量池, 按照上面说的,类加载阶段由于resolve 阶段是lazy的,所以是不会创建实例,更不会驻留字符串常量池了。但是要注意这个“static”和其他三个不一样,它是静态的,在类加载阶段中的初始化阶段,会为静态变量指定初始值,也就是要把“static”赋值给s1(main方法里面还有个s1,这里说的是外面那个静态的),这个赋值操作要怎么搞啊,先ldc指令把它放到栈顶,然后用putstatic指令完成赋值。注意,ldc指令,根据上面说的,会创建”static”字符串对象,并且会保存一个指向它的引用到字符串常量池。
  • 运行main方法后,首先是第二句,一样的,要先用ldc把”he”和”llo”送到栈顶,换句话说,会创建他俩的对象,并且会保存引用到字符串常量池中;然后有个+号对吧,内部是创建了一个StringBuilder对象,一路append,最后调用StringBuilder对象的toString方法得到一个String对象(内容是hello,注意这个toString方法会new一个String对象),并把它赋值给s1。注意啊,没有把hello的引用放入字符串常量池
  • 然后是第三句,intern方法一看,字符串常量池里面没有,它会把上面的这个hello对象的引用保存到字符串常量池,然后返回这个引用,但是这个返回值我们并没有使用变量去接收,所以没用。
  • 第四句,字符串常量池里面已经有了,直接用嘛

    再看另一段代码

class NewTest2{
    public static void main(String[] args) {
        String s1=new String("he")+new String("llo");//1
        String s2=new String("h")+new String("ello");//2
        String s3=s1.intern();//3
        String s4=s2.intern();//4
        System.out.println(s1==s3);
        System.out.println(s1==s4);
    }
}
  • 类加载阶段,什么都没干。
  • 然后运行main方法,先看第一句,会创建”he”和”llo”对象,并放入字符串常量池,然后会创建一个”hello”对象,没有放入字符串常量池,s1指向这个”hello”对象。
  • 第二句,创建”h”和”ello”对象,并放入字符串常量池,然后会创建一个”hello”对象,没有放入字符串常量池,s2指向这个”hello”对象。
  • 第三句,字符串常量池里面还没有,于是会把s1指向的String对象的引用放入字符串常量池(换句话说,放入池中的引用和s1指向了同一个对象),然后会把这个引用返回给了s3,所以s3==s1是true。
  • 第四句,字符串常量池里面已经有了,直接将它返回给了s4,所以s4==s1是true。

对于问题new一个string的时候,请看代码

String a=new String("abc");
System.out.println(a==a.intern());

输出
false,说明a这个引用是指的堆中New的地址与intern进去字符串常量池的地址不同,但是还是不能说明new String(“abc”)就没有将abc放入常量池,只可以证明返回的地址是堆中引用。

查找博客我看到一篇博客中的一张图
深入理解Java中的String
这里写图片描述
对应代码

String a = "chenssy";
String b = "chenssy";
String c = new String("chenssy");

意思是new一个string产生的对象。虽然他的引用c和通过双引号直接在字符串常量池生成的引用a和b不同,但是组成string内容的成员char[]value,却共用一个char[]value。为了验证,设计实验,思路为,改变string中的char[]value内容,来查看abc是否全都改变了。

public class StringPoolTest {
     public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException  {

            String a = "abc";
            String b="abc";
            String c = new String("abc");
            System.out.println("改变char[]value之前:");
            System.out.println("a="+ a);
            System.out.println("b="+ b);
            System.out.println("c="+ c);

            System.out.print("a与b是否都指向常量池内的地址:");
            System.out.println(a==b);

            System.out.print("a与c是否都指向常量池内的地址:");
            System.out.println(a==c);


            Field valueFieldString=String.class.getDeclaredField("value");
            valueFieldString.setAccessible(true);
            char[]value=(char[])valueFieldString.get(c);
            value[2]='@';
            System.out.println("改变char[]value之后:");

            System.out.println("a="+ a);
            System.out.println("b="+ b);
            System.out.println("c="+ c);

            System.out.print("a与b是否都指向常量池内的地址:");
            System.out.println(a==b);

            System.out.print("a与c是否都指向常量池内的地址:");
            System.out.println(a==c);

     }  
}

输出

改变char[]value之前:
a=abc
b=abc
c=abc
a与b是否都指向常量池内的地址:true
a与c是否都指向常量池内的地址:false
改变char[]value之后:
a=ab@
b=ab@
c=ab@
a与b是否都指向常量池内的地址:true
a与c是否都指向常量池内的地址:false

解释:我改变了string对象c中的char[]value的值(对于数组的赋值,我们可以改变数组对象的值而引用不变),然后输出abc发现,他们的内容全部都改变了,说明abc使用的是同一个char[]abc,也就印证了上面的那副图,也解决了我的部分疑惑:当new一个string的时候,其中一定与字符串常量池产生了联系,然后我推断,当我第一次new一个字符串的时候(不用双引号的方法),堆中会产生一个string对象,而字符串常量池中也会产生一个string对象,而且堆中的char[]value(即string内容)用的就是存在字符串常量池中的char[]value,即当问你new string会产生几个对象,我认为可以说产生了两个对象(那么栈中的引用算一个对象吗???

我还发现,我们改变了char[]value之后,相当于欺骗了jvm,因为JVM并不知道常量池中原本引用”abc”字符串的地址,其对应的内容已经改变为ab@了,对于这个问题,引入代码(https://blog.csdn.net/bagba/article/details/51801177)

public class StringPoolTest {
     public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException  {
            String a="abc";
            Field valueFieldString=String.class.getDeclaredField("value");
            valueFieldString.setAccessible(true);
            char[]value=(char[])valueFieldString.get(a);
            value[2]='@';
            String b="abc";
            //a.intern();
            System.out.println(a);
            System.out.println(b);
            System.out.println(a==b);//1
            System.out.println("abc"==b);//2
            System.out.println("ab@"==a);//3
            System.out.println(a.equals("ab@"));//4
            System.out.println(a.equals("abc"));//5
            System.out.println("abc".equals("ab@"));//6

     }
}

输出

ab@
ab@
true
true
false
true
true
true

解释:

  • //1 为true 表明a与b这两个引用的地址相同
  • //2 为true 表明JVM认为字符串常量池中仍然存在”abc”(其实已经是ab@),所以将(ab@)的地址又给了新的”abc”
  • //3 为false,也证明上面一点,即欺骗了jvm,新的ab@在字符串常量池有了新的地址
  • //4为 true equals先比较地址 不同,那么比较字符,发现完全一样,返回真
  • //5 true, 比较地址,即为相同
  • //6 true 先比较地址,不同,再比较字符,由于abc的地址中的内容其实是修改后的ab@,所以返回真

执行a.intern()之后 //3的false变为了true 我没想明白!

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;