5659 字
11 分钟
Java 面试:String 源码深度解析与面试技巧
前言
String 是 Java 中最常用的类之一,几乎每个 Java 程序都离不开它。在面试中,关于 String 的问题也非常常见,涉及字符串不可变性、字符串常量池、intern() 方法等多个知识点。
本文将深入剖析 String 的底层实现原理,帮助你在面试中从容应对。
一、面试常见问题
- String 为什么是不可变的?
- String、StringBuilder、StringBuffer 有什么区别?
- String 的 intern() 方法有什么作用?
- 字符串常量池是什么?字符串如何存储?
- new String("abc") 创建了几个对象?
二、String 的不可变性
2.1 源码分析
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
// 使用 final 修饰的字符数组存储字符串内容
private final char value[];
// 缓存的哈希值
private int hash; // Default to 0
// ...
}
2.2 不可变性的设计原因
// 1. 安全性:String 被广泛用作参数,不可变保证安全性
public void connectDatabase(String username, String password) {
// 如果 String 可变,password 可能在传递过程中被修改
database.connect(username, password);
}
// 2. 线程安全:不可变对象天然线程安全
String str = "hello";
// 多个线程可以同时读取,无需同步
// 3. 字符串常量池:不可变才能实现常量池复用
String s1 = "hello"; // 放入常量池
String s2 = "hello"; // 从常量池获取
System.out.println(s1 == s2); // true
三、字符串常量池
3.1 常量池位置演变
JDK 1.6:字符串常量池位于永久代(PermGen)
JDK 1.7:字符串常量池移至堆内存
JDK 1.8:永久代被元空间(Metaspace)取代,常量池仍在堆中
3.2 字符串创建方式对比
public class StringPoolDemo {
public static void main(String[] args) {
// 方式 1:字面量创建(推荐)
// 直接放入常量池,复用已有对象
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2); // true,同一对象
// 方式 2:new 创建
// 堆中创建新对象,不检查常量池
String s3 = new String("abc");
System.out.println(s1 == s3); // false,不同对象
System.out.println(s1 == s3.intern()); // true
// 方式 3:intern() 方法
// 将字符串放入常量池,返回常量池引用
String s4 = new String("def").intern();
String s5 = "def";
System.out.println(s4 == s5); // true
}
}
3.3 new String("abc") 创建了几个对象?
答案:1 个或 2 个
情况 1:常量池已有 "abc"
- 只创建 1 个堆中对象
情况 2:常量池没有 "abc"
- 创建 2 个对象:常量池中的 "abc" + 堆中的 String 对象
四、StringBuilder vs StringBuffer
4.1 三者对比
| 特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全(不可变) | 不安全 | 安全(synchronized) |
| 性能 | 低(频繁创建对象) | 高 | 较高(有同步开销) |
| 使用场景 | 字符串常量 | 单线程字符串操作 | 多线程字符串操作 |
4.2 StringBuilder 源码分析
public final class StringBuilder extends AbstractStringBuilder
implements java.io.Serializable, CharSequence {
public StringBuilder() {
super(16); // 默认初始容量 16
}
public StringBuilder(int capacity) {
super(capacity);
}
// append 方法(无 synchronized,线程不安全)
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
}
// 父类 AbstractStringBuilder
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value; // 非 final,可变
int count; // 实际长度
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;
}
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2; // 2 倍 + 2
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
}
4.3 性能对比测试
public class StringPerformanceTest {
public static void main(String[] args) {
int count = 100000;
// String 拼接(极慢,会创建大量对象)
long start1 = System.currentTimeMillis();
String str = "";
for (int i = 0; i < count; i++) {
str += i; // 每次创建新对象
}
System.out.println("String: " + (System.currentTimeMillis() - start1) + "ms");
// StringBuilder(最快)
long start2 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) {
sb.append(i);
}
sb.toString();
System.out.println("StringBuilder: " + (System.currentTimeMillis() - start2) + "ms");
// StringBuffer(有同步开销,稍慢)
long start3 = System.currentTimeMillis();
StringBuffer sbf = new StringBuffer();
for (int i = 0; i < count; i++) {
sbf.append(i);
}
sbf.toString();
System.out.println("StringBuffer: " + (System.currentTimeMillis() - start3) + "ms");
}
}
// 输出结果(大致比例):
// String: 3500ms+
// StringBuilder: 10ms
// StringBuffer: 15ms
七、面试回答技巧总结
核心要点记忆口诀
String 不可变,常量池里存
new 会建堆,intern 能复用
StringBuilder 单线程快
StringBuffer 多线程稳
HashMap 数组加链表
哈希冲突链表排
超过八转红黑树
扩容两倍 rehash
面试问 HashMap
从结构到原理
从 1.7 到 1.8
扩容优化讲清楚
回答框架
问题:讲讲 HashMap 的原理
建议按以下结构回答:
- 整体结构:数组 + 链表 + 红黑树(JDK 1.8)
- put 流程:计算 hash → 找位置 → 插入/替换
- 扩容机制:2 倍扩容、rehash、JDK 1.8 的优化
- 线程安全:非线程安全,ConcurrentHashMap 替代
- JDK 1.8 优化:红黑树、尾插法、扩容优化
总结
String 和 HashMap 是 Java 面试中最高频的知识点,掌握它们不仅是为了应对面试,更是理解 Java 设计思想的重要途径。
核心收获:
- 理解不可变对象的设计意义(安全、线程安全、常量池复用)
- 掌握 HashMap 的底层实现(数组 + 链表 + 红黑树)
- 理解哈希冲突的解决方案(链表法、红黑树优化)
- 掌握扩容机制和 JDK 1.8 的优化
- 理解线程安全问题及解决方案
希望本文能帮助你在面试中脱颖而出,祝面试顺利!
Java 面试:String 源码深度解析与面试技巧
https://www.zztzz.com.cn/posts/57/ 部分信息可能已经过时









