String拼接相关问题

278 阅读4分钟

 参考 尚硅谷2020最新版宋红康JVM教程更新至中篇(java虚拟机详解,jvm从入门到精通

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

  • String str1 = new String("ABC")会创建多少个对象?

    • 一个或两个。如果常量区有ABC的值,则只在堆中创建一个对象。

    • 如果常量池没有,则还会在常量池中创建”ABC"。

    • 怎么得知的呢?通过查看字节码,由ldc指令得知的。

  • String str1 = new String("A"+"B") ; 会创建多少个对象?

    • 常量池 三个 “A","B”,“AB"

    • 堆 一个 new String("AB")

    • 总共 4个

    • 对应字节码文件

       0 new #13 <java/lang/String> 3 dup 4 ldc #18 <AB> 6 invokespecial #15 <java/lang/String.<init>> 9 astore_110 return
      
  • String str2 = new String("ABC") + "ABC" ; 会创建多少个对象?

    • 对象1 new StringBuilder()

    • 对象2 new String("ABC")

    • 对象3 常量池中的 ”ABC"

    • 对象4:builder.toString() 方法相当于 new String("ABCABC") (更加深入)

    • 总共4个

    • 对应字节码文件

       0 new #6 <java/lang/StringBuilder> 3 dup 4 invokespecial #7 <java/lang/StringBuilder.<init>> 7 new #13 <java/lang/String>10 dup11 ldc #14 <ABC>13 invokespecial #15 <java/lang/String.<init>>16 invokevirtual #8 <java/lang/StringBuilder.append>19 ldc #14 <ABC>21 invokevirtual #8 <java/lang/StringBuilder.append>24 invokevirtual #10 <java/lang/StringBuilder.toString>27 astore_128 return
      
  • String str3 = new String("A") +new String("B"); 会创建多少个对象?

    • 对象1 new StringBuilder()

    • 对象2 new String("A")

    • 对象3 常量池中的“A”

    • 对象4 new String("B")

    • 对象5 常量池中的“B”

    • 对象6 builder.toString() 方法近似于 new String("AB"),强调一下,常量池里面并没有生成AB

    • 总共6个

    • 对应字节码文件:

       0 new #6 <java/lang/StringBuilder> 3 dup 4 invokespecial #7 <java/lang/StringBuilder.<init>> 7 new #13 <java/lang/String>10 dup11 ldc #16 <A>13 invokespecial #15 <java/lang/String.<init>>16 invokevirtual #8 <java/lang/StringBuilder.append>19 new #13 <java/lang/String>22 dup23 ldc #17 <B>25 invokespecial #15 <java/lang/String.<init>>28 invokevirtual #8 <java/lang/StringBuilder.append>31 invokevirtual #10 <java/lang/StringBuilder.toString>34 astore_135 return
      

String注意事项

  • 要点:

    • 常量与常量的拼接,结果放在常量池中,原理是编译期优化

    • 常量池中不会存在相同内容的对象。

    • 只要其中一个是变量,结果就放在堆中。变量拼接的原理是StringBuilder

    • 若拼接的结果调用intern()方法,则主动将常量池还没有的字符串对象放入池中,并返回此对象地址。

    • 通过StringBuilderappend()方式添加字符串的效率要远高于使用String的字符串拼接方式。

      • 前者只创建过一个StringBuilder对象, 后者在每次循环中都要创建一个新的StringBuilderString对象。

      • 后者由于内存中创建了较多的StringBuilderString对象,内存占用更大,如果进行GC,需要花费额外的时间。

  • 代码示例:

    @Testpublic void test1(){    String s1 = "a"+"b"+"c";    String s2 = "abc";    /**         执行细节:         常量池中创建了三个变量 "a" "ab" "abc"         **/    System.out.println(s1==s2);//true}​@Testpublic void test2_1(){    String s1 = "a";    String s2 = s1 + "b";    String s3 = "ab";    /**         执行细节:         1  StringBuilder s = new StringBuilder();         2  s.append("a")         3  s.append("b")         4\. s.toString(); 类似于 new String("ab")         补充 jdk5.0之后使用的是StringBuilder,以前使用StringBuffer         **/    System.out.println(s3==s2);}​@Testpublic void test2(){    String s1 = "a";    String s2 = "b";    String s3 = "ab";    /**          执行细节:            1  StringBuilder s = new StringBuilder();            2  s.append("a")            3  s.append("b")            4\. s.toString(); 类似于 new String("ab")          补充 jdk5.0之后使用的是StringBuilder,以前使用StringBuffer         **/    String s4 = s1+s2;    System.out.println(s3==s4);}​@Testpublic void test3(){    String s1 = null;    String s2 = "b";    String s3 = s1+s2;    /**         * 执行细节:         *  1 StringBuilder s = new StringBuilder()         *  2 s.append(s1)         *  3 s.append("b")         *  4 s.toString(); 类似于new String("nullb")         */    System.out.println(s3);}​@Testpublic void test4(){    final String s1 = "a";    final String s2 = "b";    String s3 = "ab";    String s4 =s1 + s2; //从字符串常量池中取的    System.out.println(s3==s4);//true}​@Testpublic void test(){    String s1 = "hello";    String s2 = "world";    String s3 = "helloworld";    String s4 = "hello" + "world";    String s5 = s1 + "world";    String s6 = "hello" + s2;    String s7 = s1 + s2;​    System.out.println(s3==s4);//true    System.out.println(s3==s5);//false    System.out.println(s3==s6);//false    System.out.println(s3==s7);//false    System.out.println(s5==s6);//false    System.out.println(s5==s7);//false    System.out.println(s6==s7);//false​    String s8 = s6.intern();    System.out.println(s3==s8);//true}
    
  • StringBuilder的append()方法:

  • private StringBuilder append(StringBuilder sb) { if (sb == null) return append("null"); int len = sb.length(); int newcount = count + len; if (newcount > value.length) expandCapacity(newcount); sb.getChars(0, len, value, count); count = newcount; return this;}

intern()方法

  • jdk6: 执行intern()方法时,若常量池中不存在等值的字符串,JVM就会在池中创建一个等值的字符串,然后返回该字符串的引用。

  • jdk7: 执行intern()方法时,若常量池中已存在该字符串,则直接返回字符串引用,否则复制该字符串的引用到常量池中并返回。

  • 例子:

        public static void main(String[] args) {        String  s = new String("1"); //创建了两个对象,一个堆中的“1”,一个常量池中的“1”        s.intern(); //没有作用,因为常量池中有"1"String s2 = "1";        System.out.println(s==s2);// jdk6 false  jdk8 falseString s3 = new String("1")+new String("1");//虽然创建了6个对象,但常量池中没有“11”        s3.intern();//对于1.8来说,直接复制引用到常量池。对于1.6则是创建了一个新对象        String s4 = "11";        System.out.println(s3==s4);// jdk6 false jdk8 true            }
    
  • 分析