MST

星途 面试题库

面试题:Java中String、StringBuffer和StringBuilder底层实现对垃圾回收的影响

从Java垃圾回收机制的角度出发,分析String、StringBuffer和StringBuilder由于底层实现差异,在垃圾回收过程中有何不同表现?如何在编写代码时,根据这种差异优化程序的垃圾回收性能?
14.2万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

1. 底层实现差异

  • String:String类是不可变类,一旦创建,其值就不能被改变。底层使用char[]数组存储字符串,并且被final修饰。每次对String进行修改操作(如拼接、替换等)时,都会创建一个新的String对象。
String str = "hello";
str = str + " world";

上述代码中,str实际上指向了一个新创建的字符串对象,原有的"hello"对象不再被引用。

  • StringBuffer:StringBuffer是可变类,线程安全,底层同样使用char[]数组存储字符串,但数组没有被final修饰。它提供了一系列方法来修改字符串内容,如appendinsert等。在扩容时,会创建一个新的更大的数组,并将原数组内容复制到新数组。
StringBuffer sb = new StringBuffer("hello");
sb.append(" world");

这里sb对象始终是同一个,通过方法修改其内部的char[]数组内容。

  • StringBuilder:StringBuilder也是可变类,线程不安全,底层实现与StringBuffer类似,同样使用可变的char[]数组。它的方法与StringBuffer基本相同,但由于没有线程同步机制,性能更高。
StringBuilder sbuilder = new StringBuilder("hello");
sbuilder.append(" world");

同样,sbuilder对象始终不变,修改操作直接在内部数组上进行。

2. 垃圾回收过程中的不同表现

  • String:由于每次修改都会创建新对象,会导致大量临时对象的产生。这些临时对象在不再被引用时,会成为垃圾对象等待垃圾回收器回收。频繁的字符串操作可能使堆内存中产生大量垃圾,增加垃圾回收的频率和压力,降低程序性能。
  • StringBuffer和StringBuilder:它们对字符串的修改是在自身对象的内部数组上进行,不会频繁创建新的对象。因此,在垃圾回收方面,相对String来说,产生的垃圾对象较少,垃圾回收压力较小。不过,当它们进行扩容操作时,会创建新的数组并复制原数组内容,原数组可能会成为垃圾对象,但这种情况相对String的频繁创建新对象来说要少得多。

3. 优化程序的垃圾回收性能

  • 避免不必要的String对象创建:在进行字符串拼接等操作时,如果不需要线程安全,优先使用StringBuilder;如果需要线程安全,则使用StringBuffer。
// 不推荐,会产生大量临时String对象
String result = "";
for (int i = 0; i < 1000; i++) {
    result = result + i;
}

// 推荐使用StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}
String result2 = sb.toString();
  • 合理预估容量:在创建StringBuffer或StringBuilder对象时,尽量预估字符串的长度,设置合适的初始容量,减少扩容次数。
// 预估长度为1000
StringBuilder sb = new StringBuilder(1000);
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}

这样可以避免在添加字符过程中频繁扩容导致的数组复制和旧数组成为垃圾对象的情况,从而优化垃圾回收性能。