String 拼接相关问题

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

常见面试题

一、new String(“xxx”)到底创建几个对象?

1. String str1 = new String(“ABC”)会创建多少个对象?

  • 一个或两个。如果常量区有ABC的值,则只在堆中创建一个对象。
  • 如果常量池没有,则还会在常量池中创建”ABC”。
  • 怎么得知的呢?通过查看字节码,由ldc指令得知的。

2. String str1 = new String(“A”+”B”) ; 会创建多少个对象?

  • 常量池 三个 “A”,”B”,“AB”
  • 堆 一个 new String("AB")
  • 总共 4个
  • 对应字节码文件java 0 new #13 <java/lang/String> //new String("AB"); 3 dup 4 ldc #18 <AB> // 常量池 "A" "B" "AB" 6 invokespecial #15 <java/lang/String.<init>> 9 astore_1 10 return

3.String str2 = new String(“ABC”) + “ABC” ; 会创建多少个对象?

  • 对象1 new StringBuilder()
  • 对象2 new String("ABC")
  • 对象3 常量池中的 ”ABC”
  • 对象4:builder.toString() 方法类似于 new String("ABCABC") (接收的是char数组,而非“ABCABC”常量,故常量池中没有”ABCABC”)
  • 总共4个
  • 对应字节码文件java 0 new #6 <java/lang/StringBuilder> // new StringBuilder() 3 dup 4 invokespecial #7 <java/lang/StringBuilder.<init>> 7 new #13 <java/lang/String> // new String("ABC") 10 dup 11 ldc #14 <ABC> // 常量池 "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> // toString() 27 astore_1 28 return

4. 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个

  • 对应字节码文件:

    1. 0 new #6 <java/lang/StringBuilder>
    2. 3 dup
    3. 4 invokespecial #7 <java/lang/StringBuilder.<init>>
    4. 7 new #13 <java/lang/String>
    5. 10 dup
    6. 11 ldc #16 <A>
    7. 13 invokespecial #15 <java/lang/String.<init>>
    8. 16 invokevirtual #8 <java/lang/StringBuilder.append>
    9. 19 new #13 <java/lang/String>
    10. 22 dup
    11. 23 ldc #17 <B>
    12. 25 invokespecial #15 <java/lang/String.<init>>
    13. 28 invokevirtual #8 <java/lang/StringBuilder.append>
    14. 31 invokevirtual #10 <java/lang/StringBuilder.toString>
    15. 34 astore_1
    16. 35 return

二、String拼接的底层是怎么做的?

知识点

  • 常量与常量的拼接,结果放在常量池中,原理是编译期优化
  • 常量池中不会存在相同内容的对象。
  • 只要其中一个是变量,结果就放在堆中。变量拼接的原理是StringBuilder
  • 若拼接的结果调用intern()方法,则主动将常量池还没有的字符串对象放入池中,并返回此对象地址。
  • 通过StringBuilderappend()方式添加字符串的效率要远高于使用String的字符串拼接方式。

    • 前者只创建过一个StringBuilder对象, 后者在每次循环中都要创建一个新的StringBuilderString对象。
    • 后者由于内存中创建了较多的StringBuilderString对象,内存占用更大,如果进行GC,需要花费额外的时间。

代码示例

  1. @Test
  2. public void test1(){
  3. String s1 = "a"+"b"+"c";
  4. String s2 = "abc";
  5. /**
  6. 执行细节:
  7. 常量池中创建了三个变量 "a" "ab" "abc"
  8. **/
  9. System.out.println(s1==s2);//true
  10. }
  11. @Test
  12. public void test2_1(){
  13. String s1 = "a";
  14. String s2 = s1 + "b";
  15. String s3 = "ab";
  16. /**
  17. 执行细节:
  18. 1 StringBuilder s = new StringBuilder();
  19. 2 s.append("a")
  20. 3 s.append("b")
  21. 4. s.toString(); 类似于 new String("ab")
  22. 补充 jdk5.0之后使用的是StringBuilder,以前使用StringBuffer
  23. **/
  24. System.out.println(s3==s2);
  25. }
  26. @Test
  27. public void test2(){
  28. String s1 = "a";
  29. String s2 = "b";
  30. String s3 = "ab";
  31. /**
  32. 执行细节:
  33. 1 StringBuilder s = new StringBuilder();
  34. 2 s.append("a")
  35. 3 s.append("b")
  36. 4. s.toString(); 类似于 new String("ab")
  37. 补充 jdk5.0之后使用的是StringBuilder,以前使用StringBuffer
  38. **/
  39. String s4 = s1+s2;
  40. System.out.println(s3==s4);
  41. }
  42. @Test
  43. public void test3(){
  44. String s1 = null; //不理解的话见下方的StringBuilder的append()方法
  45. String s2 = "b";
  46. String s3 = s1+s2;
  47. /**
  48. * 执行细节:
  49. * 1 StringBuilder s = new StringBuilder()
  50. * 2 s.append(s1)
  51. * 3 s.append("b")
  52. * 4 s.toString(); 类似于new String("nullb")
  53. */
  54. System.out.println(s3);
  55. }
  56. @Test
  57. public void test4(){
  58. final String s1 = "a";
  59. final String s2 = "b";
  60. String s3 = "ab";
  61. String s4 =s1 + s2; //从字符串常量池中取的
  62. System.out.println(s3==s4);//true
  63. }
  64. @Test
  65. public void test(){
  66. String s1 = "hello";
  67. String s2 = "world";
  68. String s3 = "helloworld";
  69. String s4 = "hello" + "world";
  70. String s5 = s1 + "world";
  71. String s6 = "hello" + s2;
  72. String s7 = s1 + s2;
  73. System.out.println(s3==s4);//true
  74. System.out.println(s3==s5);//false
  75. System.out.println(s3==s6);//false
  76. System.out.println(s3==s7);//false
  77. System.out.println(s5==s6);//false
  78. System.out.println(s5==s7);//false
  79. System.out.println(s6==s7);//false
  80. String s8 = s6.intern();
  81. System.out.println(s3==s8);//true
  82. }
  • PS: StringBuilder的append()方法:
  1. private StringBuilder append(StringBuilder sb) {
  2. if (sb == null)
  3. return append("null");
  4. int len = sb.length();
  5. int newcount = count + len;
  6. if (newcount > value.length)
  7. expandCapacity(newcount);
  8. sb.getChars(0, len, value, count);
  9. count = newcount;
  10. return this;
  11. }

String的intern()方法

  • jdk6: 执行intern()方法时,若常量池中不存在等值的字符串,JVM就会在池中创建一个等值的字符串,然后返回该字符串的引用。
  • jdk7: 执行intern()方法时,若常量池中已存在该字符串,则直接返回字符串引用,否则复制该字符串的引用到常量池中并返回。
  • 例子:
  1. public static void main(String[] args) {
  2. String s = new String("1"); //创建了两个对象,一个堆中的“1”,一个常量池中的“1”
  3. s.intern(); //没有作用,因为常量池中有"1"了
  4. String s2 = "1";
  5. System.out.println(s==s2);// jdk6 false jdk8 false
  6. String s3 = new String("1")+new String("1");//虽然创建了6个对象,但常量池中没有“11”
  7. s3.intern();//对于1.8来说,直接复制引用到常量池。对于1.6则是创建了一个新对象
  8. String s4 = "11";
  9. System.out.println(s3==s4);// jdk6 false jdk8 true
  10. }
  • 分析:
    String 拼接相关问题 - 图1