一、String、StringBuffer、StringBuilder的区别?
可变性
String 是不可变的(后面会详细分析原因)。
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 final 和 private 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。
abstract class AbstractStringBuilder implements Appendable, CharSequence {char[] value;public AbstractStringBuilder append(String str) {if (str == null)return appendNull();int len = str.length();ensureCapacityInternal(count + len);str.getChars(0, len, value, count);count += len;return this;}
}
线程安全性
String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
性能
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
操作少量的数据: 适用 String
单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
二、在什么情况下使用String,什么情况下使用StringBuffer和StringBuilder?
- 使用 String 的情况
- 字符串常量表示:当表示不会被修改的字符串常量时,应该使用
String
。例如,定义一个表示程序版本号的字符串:
- 字符串常量表示:当表示不会被修改的字符串常量时,应该使用
String version = "1.0.0";
- 少量字符串拼接操作(在 Java 编译器优化下):对于一些简单的、少量的字符串拼接,Java 编译器会进行优化,使用
String
也可以。例如:
String name = "John";String greeting = "Hello, " + name;
在这种情况下,Java 编译器可能会将其优化为一个StringBuilder
的操作(在字节码层面),所以对于这种简单的拼接场景,使用String
是可以的。
- 方法参数传递和返回值(不需要修改的情况):当把字符串作为参数传递给一个方法,并且这个方法不会修改字符串内容,或者方法的返回值是一个不会被修改的字符串时,使用
String
。例如:
public class StringUsage {public static void printString(String str) {System.out.println(str);}public static String getString() {return "This is a sample string";}}
- 使用 StringBuffer 的情况
- 多线程环境下的字符串修改操作:在多线程环境中,如果多个线程需要对同一个字符串进行修改,就需要使用
StringBuffer
来保证线程安全。例如,一个简单的日志记录类,多个线程可能会同时向日志中添加内容:
- 多线程环境下的字符串修改操作:在多线程环境中,如果多个线程需要对同一个字符串进行修改,就需要使用
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class StringBufferInMultiThread {private static StringBuffer logBuffer = new StringBuffer();public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(5);for (int i = 0; i < 10; i++) {executor.submit(() -> {logBuffer.append("Log entry from a thread\n");});}executor.shutdown();while (!executor.isTerminated()) {// Wait for all threads to finish}System.out.println(logBuffer.toString());}}
在这个例子中,多个线程通过executor.submit
方法提交任务,每个任务都向StringBuffer
对象logBuffer
中添加一条日志记录。由于StringBuffer
是线程安全的,所以可以正确地记录所有线程的日志内容。
3. 使用 StringBuilder 的情况
- 单线程环境下的字符串修改操作:在单线程环境中,当需要频繁地修改字符串,如进行大量的字符串拼接、插入或删除操作时,使用
StringBuilder
可以获得更好的性能。例如,构建一个包含大量元素的 HTML 字符串:
public class StringBuilderUsage {public static void main(String[] args) {StringBuilder htmlBuilder = new StringBuilder();htmlBuilder.append("<html><body>");for (int i = 0; i < 100; i++) {htmlBuilder.append("<p>Paragraph " + i + "</p>");}htmlBuilder.append("</body></html>");System.out.println(htmlBuilder.toString());}}
在这个例子中,在单线程环境下使用StringBuilder
来构建一个包含 100 个段落的 HTML 页面内容。由于没有多线程安全的开销,StringBuilder
可以高效地完成字符串的拼接操作。
三、String#equals()和Object#equals()有何区别?
String
中的 equals
方法是被重写过的,比较的是 String 字符串的值是否相等。 Object
的 equals
方法是比较的对象的内存地址。
四、String#intern 方法有什么作用?
String.intern()
是一个 native
(本地) 方法,用来处理字符串常量池中的字符串对象引用。它的工作流程可以概括为以下两种情况:
- 常量池中已有相同内容的字符串对象:如果字符串常量池中已经有一个与调用
intern()
方法的字符串内容相同的String
对象,intern()
方法会直接返回常量池中该对象的引用。 - 常量池中没有相同内容的字符串对象:如果字符串常量池中还没有一个与调用
intern()
方法的字符串内容相同的对象,intern()
方法会将当前字符串对象的引用添加到字符串常量池中,并返回该引用。
总结:
intern()
方法的主要作用是确保字符串引用在常量池中的唯一性。- 当调用
intern()
时,如果常量池中已经存在相同内容的字符串,则返回常量池中已有对象的引用;否则,将该字符串添加到常量池并返回其引用。