String的一些总结(有一定String基础的再看下面的那些东西,不好理解)& StringBuilder & StringBuffer & 常量池 & intern方法

String总结

!!!从经典例题开始说 String : String str = new String(“abc”);创建了几个对象,为什么?

可能是一个,也可能是两个。解释:如果此时常量池已经有“abc",那么就只在堆中创建一个对象,如果常量池没有”abc“,那么会在常量池创建”abc“,并且在堆中创建”abc“,且不管是哪种情况,str一定指向的是堆中的”abc“

1、String是基本数据类型吗?

String是java lang包下的一个类,不像基本数据类型int long等。
String是引用数据类型,不是基本数据类型。

2、String,Stringbuffer,StringBuilder

String

String是Java中基础且重要的类,被声明为final class,除了hash这个属性其他属性都声明为final,因为它的不可变性,所以如果拼接字符串时候会产生很多无用的中间对象,如果频繁的进行这样的操作对性能有所影响。

StringBuffer

StringBuffer就是为了解决大量拼接字符串时产生很多中间对象问题而提供的一个类,它提供了append和add方法,可以将字符串添加到已有序列的末尾或者指定位置,它的本质是一个线程安全的可修改的字符序列,把所有修改数据的方法都加上了synchronized

StringBuilder

但是保证线程安全是有性能代价的。在很多情况下我们的字符串拼接操作不需要线程安全,这时候StringBuilfer登场了,StringBuilder是JDK1.5发布的,StringBuilder和 StringBuffer本质上没有什么区别,就是去掉了保证线程安全的那部分,减少了开销。

String是不可变的,String类中的char数组也是final修饰的不可变

String类被final修饰, 所谓不可变类,是指当创建了这个类的实例后,就不允许修改它的属性值。在java中,所有基本类型的包装类,如Integer和Long类,都是不可变类,java.lang.String也是不可变类,虽然他不是基本类型。

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

https://blog.csdn.net/weixin_30360497/article/details/102158260

https://blog.csdn.net/wilbur_xieyj/article/details/102745860

public static void main(String[] args) {
       String s1 = "hello";
       String s2 = "hello";
       String s3 = "he" + "llo";
       String s4 = "hel" + new String("lo");
       String s5 = new String("hello");
       String s6 = s5.intern();
       String s7 = "h";
       String s8 = "ello";
       String s9 = s7 + s8;
       System.out.println(s1==s2);//true	s1执行创建的时候,由于常量池没有hello,那么他在添加了hello,并返回其引用,  s2创建的时候,由于常量池已经有hello,那么直接返回引用。
       System.out.println(s1==s3);//true	在Java中有一种叫做常量优化的机制,我们在赋值的时候 “he”,“llo”都是常量,系统就认为是 s3 = "hello";其中s3在编译后就已经将后面两个字符串合并了,反编译后String s3 = “hello”;
       System.out.println(s1==s4);//false	s4:创建了两个对象,其中“hel”在常量池中,“lo”在堆中,本质上是两个对象相加,不会被编译器优化,相加结果在堆中。与s1不一致,参考s9对比一下
       System.out.println(s1==s5);//false, 回头看最开始引出的那个面试例题,此时其实只是单纯的在堆中创建一个hello对象并返回其引用赋值给s5
       System.out.println(s1==s9);//false
       System.out.println(s4==s5);//false	堆中两个不同对象
       System.out.println(s1==s6);//true	intern方法,如果如果常量池有hello,直接返回指向hello的引用(此时就是这样),
       System.out.println(s1==s9);//false	Java 语言提供对字符串串联符号("+")以及将其他对象转换为字符串的特殊支持。字符串串联是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的。字符串转换是通过 toString 方法实现的  将s9改成: s9 = s7 + "ello"是一样的道理,最终也是false
    
}
public static void main(String[] args) {
        String s1=new String("he")+new String("llo");     // 1
        s1.intern();                                      // 2
        String s2="hello";                                // 3
        System.out.println(s1==s2);                       // 4
}

我们一行行来看。执行第1行代码时,首先会在堆中创建"he"和"llo"的对象,然后再将它们引用保存到字符串常量池中,然后有个+号对吧,内部是创建了一个StringBuilder对象,一路append,最后调用StringBuilder对象的toString方法得到一个String对象(内容是hello,注意这个toString方法会new一个String对象),并把它赋值给s1。注意啊,没有把hello的引用放入字符串常量池。

然后来到第2行,执行s1.intern(),jvm首先会到字符串常量池中寻找字符串"hello",发现并不存在,这时jvm会将s1对象的引用保存到字符串常量池中,然后返回这个引用,但这个引用没有被接收,所以没有用。

到了第3行,这时字符串常量池中已经有"hello"了,直接用。

第4行,s1表示在堆中的对象"hello"的引用,而s2拿到的"hello"则是堆中"hello"对象在字符串中的一个引用,所以它们指向了同一个对象,所以返回为true。

但是如果顺序是这样:结果却是false

public static void main(String[] args) {
        String s1=new String("he")+new String("llo");     // 1
   	    String s2="hello";                                // 3
        s1.intern();                                      // 2
        System.out.println(s1==s2);                       // 4
}

原因是s2在创建的时候,在常量池创建了hello,那么s1再调intern方法时,返回的是常量池hello的引用,而不是常量池hello指向s1的引用

String s1=new String("he")+new String("llo");   // 1. 新建一个引用s1指向堆中的对象s1,值为"hello"
String s2=new String("h")+new String("ello");   // 2. 新建一个引用s2指向堆中的对象s2,值为"hello"
String s3=s1.intern();  // 3. 执行s1.intern()会在字符串常量池中新建一个引用"hello",该引用指向s1在堆中的地址,并新建一个引用s3指向字符串常量池中的"hello"
String s4=s2.intern();  // 4. 执行s2.intern()不会在字符串常量池中创建新的引用,因为"hello"已存在,因此只执行了新建一个引用s4指向字符串常量池中"hello"的操作
System.out.println(s1==s3); //true	 s3和s4指向的都是字符串常量池中的"hello",而这个"hello"都指向堆中s1的地址,因此下面两句代码都为true
System.out.println(s1==s4);// true

System.out.println(s2 == s3);//false	 s3和s4最终关联堆中的地址是对象s1,因此下面两句为false
System.out.println(s2 == s4);//false

再来看上面这几行代码。第1行首先在堆中创建"he"和"llo"的对象,并将它们的引用放入字符串常量池,然后在堆中创建一个"hello"对象,没有放到字符串常量池,s1指向这个"hello"对象。

第2行在堆中创建"h"和"ello"对象,并放入字符串常量池,然后在堆中创建一个"hello"对象,没有放到字符串常量池,s2指向这个"hello"对象。

第3行,字符串常量池里没有"hello",因此会把s1指向的堆中的"hello"对象的引用放入字符串常量池(也就是说字符串常量池中的引用和s1指向了同一个对象),然后把这个引用返回给了s3,所以呢执行s3 == s1为true;

第4行,字符串常量池里已经有"hello"了,因此直接将它返回给了s4,所以s4 == s1也为true。

至于s2 == s3和s2 == s4为false则很明显了吧,s3和s4指向的字符串常量池中的引用和s1指向的对象是同一个,而s2则指向了另一个对象,因此返回false。

intern方法的注意

String s = new Sting("hello");
System.out.println(s.intern()==s);    // false

第一行代码会在堆中创建两个对象(记为一号本体a1和二号本体a2),字符串常量池中存的是a1的引用,二号本体a2在字符串常量池无引用,s指向的是二号本体a2。然后s.intern()返回的是一号本体a1与二号本体肯定不同,所以返回false。

img

再看下面这段代码

String s1=new String("he")+new String("llo");
System.out.println(s1.intern()==s1);

执行第一段代码只在常量池中放了"he"和"llo"的引用,并没有放"hello"的引用,所以执行到s1.intern()时,会把s1的引用保存到常量池中,因此s1.intern()返回的引用与s1指向同一个地址,因此为true。

  String s = new String("1");
  
  s.intern();
    
  String s2 = "1";
    
  System.out.println(s == s2);	//	false
    
  String s3 = new String("1") + new String("1");
  
  s3.intern();
  
  String s4 = "11";
    
  System.out.println(s3 == s4); // true

intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。
那么其他字符串在常量池找值时就会返回另一个堆中对象的地址。

posted @ 2021-06-14 11:22  PocketHeart  阅读(89)  评论(0编辑  收藏  举报