String 拼接相关问题
常见面试题
一、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个
对应字节码文件:
0 new #6 <java/lang/StringBuilder>
3 dup
4 invokespecial #7 <java/lang/StringBuilder.<init>>
7 new #13 <java/lang/String>
10 dup
11 ldc #16 <A>
13 invokespecial #15 <java/lang/String.<init>>
16 invokevirtual #8 <java/lang/StringBuilder.append>
19 new #13 <java/lang/String>
22 dup
23 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_1
35 return
二、String拼接的底层是怎么做的?
知识点
- 常量与常量的拼接,结果放在常量池中,原理是编译期优化
- 常量池中不会存在相同内容的对象。
- 只要其中一个是变量,结果就放在堆中。变量拼接的原理是
StringBuilder
。 - 若拼接的结果调用
intern()
方法,则主动将常量池还没有的字符串对象放入池中,并返回此对象地址。 通过
StringBuilder
的append()
方式添加字符串的效率要远高于使用String
的字符串拼接方式。- 前者只创建过一个
StringBuilder
对象, 后者在每次循环中都要创建一个新的StringBuilder
和String
对象。 - 后者由于内存中创建了较多的
StringBuilder
和String
对象,内存占用更大,如果进行GC,需要花费额外的时间。
- 前者只创建过一个
代码示例
@Test
public void test1(){
String s1 = "a"+"b"+"c";
String s2 = "abc";
/**
执行细节:
常量池中创建了三个变量 "a" "ab" "abc"
**/
System.out.println(s1==s2);//true
}
@Test
public 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);
}
@Test
public 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);
}
@Test
public void test3(){
String s1 = null; //不理解的话见下方的StringBuilder的append()方法
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);
}
@Test
public void test4(){
final String s1 = "a";
final String s2 = "b";
String s3 = "ab";
String s4 =s1 + s2; //从字符串常量池中取的
System.out.println(s3==s4);//true
}
@Test
public 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
}
- PS:
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;
}
String的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 false
String 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
}
- 分析: