JavaString知识梳理

作者:没有四次元口袋的蓝胖

日期:2026-07-12

标签:Java, String

Java String知识梳理

一、String 的不可变性

String 是不可变的(immutable),一旦创建,其值就不能修改。

1.1 为什么设计成不可变?

原因

  1. 线程安全:不可变对象天然线程安全,无需同步
  2. 字符串常量池:可以共享相同字符串,节省内存
  3. hashCode 缓存:创建时计算一次 hashCode,后续直接使用(HashMap 的 key)
  4. 安全性:作为参数传递时不会被篡改(数据库连接、网络地址等)

1.2 源码分析

java 复制代码
public final class String {
    private final char value[];  // final 修饰,引用不可变
    // Java 9+ 改为:private final byte[] value;
    
    public String(String original) {
        this.value = original.value;  // 直接复用
        // ...
    }
}

关键

  • final class String:String 类不可继承
  • final char[] value:数组引用不可变(但数组内容理论上可变,Java 通过不提供修改方法保证)

1.3 "不可变"的陷阱

java 复制代码
String str = "hello";
str = str + " world";  // 看起来修改了,实际上创建了新对象

// 实际过程:
// 1. 创建 "hello"
// 2. 创建 " world"
// 3. 创建新字符串 "hello world"
// 4. str 引用指向新字符串
// 5. 原来的 "hello" 还在(可能被其他引用使用)

💡 String 的"修改"都是创建新对象,原对象不变。


二、字符串常量池

2.1 常量池的作用

字符串常量池(String Pool)是 JVM 堆内存中的一块特殊区域,用于存储字符串字面量。

优点

  • 节省内存:相同字符串共享
  • 提高效率:避免重复创建

2.2 创建方式的区别

java 复制代码
// 方式1:字面量(常量池)
String s1 = "hello";

// 方式2:new 对象(堆内存)
String s2 = new String("hello");

// 方式3:拼接(编译器优化)
String s3 = "hel" + "lo";  // 编译期优化为 "hello"

2.3 == vs equals

java 复制代码
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = new String("hello");

System.out.println(s1 == s2);        // true(同一个常量池对象)
System.out.println(s1 == s3);        // false(s3 在堆中)
System.out.println(s3 == s4);        // false(两个不同的对象)
System.out.println(s1.equals(s3));   // true(内容相同)

规则

  • ==:比较引用(内存地址)
  • equals():比较内容

⚠️ String 必须用 equals() 比较内容,不能用 ==。

2.4 intern() 方法

intern() 会将字符串放入常量池(如果不存在),并返回常量池中的引用。

java 复制代码
String s1 = new String("hello");  // 堆中创建
String s2 = s1.intern();          // 将 "hello" 放入常量池(如果不存在)

String s3 = "hello";  // 从常量池获取

System.out.println(s1 == s2);  // false(s1 在堆,s2 在常量池)
System.out.println(s2 == s3);  // true(都是常量池中的 "hello")

Java 6 vs Java 7+

  • Java 6:常量池在永久代(PermGen),intern() 会复制字符串
  • Java 7+:常量池移到堆中,intern() 不会复制,只是添加引用

三、字符串拼接

3.1 拼接方式对比

方式 类型 线程安全 性能 适用场景
+ 语法糖 安全 低(每次创建新对象) 少量拼接
StringBuilder 可变 不安全 单线程大量拼接
StringBuffer 可变 安全 中(有同步开销) 多线程拼接
String.concat() 方法 安全 不常用

3.2 编译器优化

java 复制代码
// 编译期常量拼接(优化)
String s1 = "hel" + "lo";  // 编译为 "hello"

// 运行期拼接(不优化)
String a = "hel";
String b = "lo";
String s2 = a + b;  // 运行期拼接,创建新对象

// final 修饰(优化)
final String a = "hel";
final String b = "lo";
String s3 = a + b;  // 编译期优化为 "hello"

3.3 StringBuilder vs StringBuffer

java 复制代码
// StringBuilder(非线程安全,性能高)
StringBuilder sb1 = new StringBuilder();
sb1.append("hello");
sb1.append(" ");
sb1.append("world");
String result1 = sb1.toString();

// StringBuffer(线程安全,性能低)
StringBuffer sb2 = new StringBuffer();
sb2.append("hello");
sb2.append(" ");
sb2.append("world");
String result2 = sb2.toString();

底层实现

java 复制代码
// StringBuffer 的 append 方法(有 synchronized)
public synchronized StringBuffer append(String str) {
    super.append(str);
    return this;
}

// StringBuilder 的 append 方法(无 synchronized)
public StringBuilder append(String str) {
    super.append(str);
    return this;
}

选择建议

  • 单线程:用 StringBuilder
  • 多线程:用 StringBuffer
  • 少量拼接:直接用 +

四、String 常见方法

4.1 常用方法速查

java 复制代码
String str = "Hello World";

// 长度
str.length();  // 11

// 字符操作
str.charAt(0);  // 'H'
str.toCharArray();  // ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd']

// 截取
str.substring(0, 5);  // "Hello"

// 查找
str.indexOf("World");  // 6
str.lastIndexOf("l");  // 9
str.contains("World");  // true
str.startsWith("Hello");  // true
str.endsWith("World");  // true

// 大小写
str.toLowerCase();  // "hello world"
str.toUpperCase();  // "HELLO WORLD"

// 去除空白
"  hello  ".trim();  // "hello"

// 替换
str.replace("World", "Java");  // "Hello Java"
str.replaceAll("[aeiou]", "*");  // "H*ll* W*rld"

// 分割
"a,b,c".split(",");  // ["a", "b", "c"]

// 比较
str.equals("Hello World");  // true
str.equalsIgnoreCase("hello world");  // true
str.compareTo("Hello Java");  // 正数(字典序)

// 格式化
String.format("Name: %s, Age: %d", "Tom", 20);  // "Name: Tom, Age: 20"

4.2 性能陷阱

java 复制代码
// 错误:循环中用 + 拼接(每次都创建新对象)
String result = "";
for (int i = 0; i < 1000; i++) {
    result += i;  // 创建 1000 个 String 对象
}

// 正确:用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}
String result = sb.toString();

五、String 与其他类型转换

5.1 String ↔ 基本类型

java 复制代码
// String → int
int num1 = Integer.parseInt("123");  // 123
int num2 = Integer.valueOf("123");   // 123

// int → String
String s1 = String.valueOf(123);     // "123"
String s2 = Integer.toString(123);   // "123"
String s3 = 123 + "";                // "123"(不推荐)

// String → double
double d = Double.parseDouble("3.14");

// String → boolean
boolean b = Boolean.parseBoolean("true");

5.2 String ↔ 字符数组

java 复制代码
// String → char[]
char[] chars = "hello".toCharArray();

// char[] → String
String s1 = new String(chars);
String s2 = String.valueOf(chars);

// String → byte[](编码)
byte[] bytes = "你好".getBytes("UTF-8");

// byte[] → String(解码)
String s3 = new String(bytes, "UTF-8");

六、常见面试题

6.1 String、StringBuffer、StringBuilder 的区别?

对比 String StringBuffer StringBuilder
可变性 不可变 可变 可变
线程安全 安全(不可变) 安全(synchronized) 不安全
性能 低(每次创建新对象)
适用 少量操作 多线程拼接 单线程拼接

6.2 String 为什么是不可变的?

  1. 安全性:作为参数传递时不会被篡改
  2. 线程安全:不可变对象天然线程安全
  3. hashCode 缓存:创建时计算一次,HashMap 的 key 效率高
  4. 字符串常量池:可以共享相同字符串

6.3 String s = new String("hello") 创建了几个对象?

答案:可能创建 1 个或 2 个对象。

情况 1:常量池中已有 "hello"

  • 只创建 1 个对象(堆中的 String 对象)
  • "hello" 字面量在常量池,new 的对象在堆中

情况 2:常量池中没有 "hello"

  • 创建 2 个对象
  • 1 个在常量池("hello" 字面量)
  • 1 个在堆中(new String)

6.4 如何判断 String 是否为空?

java 复制代码
// 错误:会 NPE
if (str.equals("")) {}

// 推荐
if (str == null || str.isEmpty()) {}  // Java 8
if (str == null || str.length() == 0) {}  // 兼容老版本

// Java 11+
if (str == null || str.isBlank()) {}  // 包含空白字符

6.5 String.intern() 的作用?

  1. 检查常量池中是否存在该字符串
  2. 存在则返回常量池中的引用
  3. 不存在则将字符串放入常量池,返回引用

应用场景

  • 节省内存(大量重复字符串)
  • 字符串比较(用 == 代替 equals)

七、思维导图速览

复制代码
┌───────────────────────────────────────────────────┐
│              String 高频考点                       │
├───────────────────────────────────────────────────┤
│                                                   │
│  不可变性                                          │
│  • final class String                             │
│  • final char[] value                             │
│  • 原因:安全/线程安全/hashCode缓存/常量池           │
│                                                   │
│  常量池                                            │
│  • 字符串字面量存储在常量池                         │
│  • new String() 在堆中创建                         │
│  • intern() 放入常量池                             │
│  • == vs equals                                   │
│                                                   │
│  拼接                                              │
│  • +:语法糖,编译器优化                            │
│  • StringBuilder:单线程,性能高                    │
│  • StringBuffer:多线程,性能中                     │
│                                                   │
│  常见方法                                          │
│  • length/charAt/substring/indexOf/contains       │
│  • replace/replaceAll/split/trim                  │
│  • equals/equalsIgnoreCase/compareTo              │
│                                                   │
│  面试题                                            │
│  • String vs StringBuffer vs StringBuilder        │
│  • 为什么不可变                                    │
│  • new String("hello") 创建几个对象                │
│  • intern() 的作用                                 │
│                                                   │
│  陷阱                                              │
│  ✗ 用 == 比较 String                              │
│  ✗ 循环中用 + 拼接                                 │
│  ✗ str.equals("") 会 NPE                          │
│  ✓ 用 equals() 比较内容                            │
│  ✓ 用 StringBuilder 拼接                          │
│  ✓ str == null || str.isEmpty() 判空              │
└───────────────────────────────────────────────────┘

八、写在最后

  1. String 不可变性 是核心,要能说出 4 个原因和源码实现
  2. 字符串常量池 是高频考点,要理解 new String() 和字面量的区别
  3. StringBuilder vs StringBuffer 要知道什么时候用哪个
  4. 常见陷阱== vs equals()、循环中用 + 拼接、空指针判断
相关推荐
咏方舟【长江支流】24 分钟前
【开源】跨语言·跨平台·跨数据库(2) —— 用宝轻量框架设计必读
数据库·架构·开源·ai编程·咏方舟-长江支流·用宝框架
手握风云-1 小时前
Redis:不只是缓存那么简单(十五)
数据库·redis·缓存
weixin_307779131 小时前
Linux下Docker Compose里运行PostgreSQL数据库故障诊断Shell脚本
linux·运维·数据库·docker·postgresql
醉城夜风~1 小时前
MySQL 常用内置函数全解:字符串、数值、日期、流程控制实战教程
数据库·mysql
程序猿秃头之路2 小时前
DDD 系列:实体和值对象详解
数据库·oracle·ddd·领域驱动设计
Database_Cool_2 小时前
阿里云 RDS MySQL vs 腾讯云 CDB 国内云关系型数据库深度对比
数据库·mysql·阿里云
xiaohaiAIgeo2 小时前
【2026年】基于三维建模的实验室智慧管理平台:暖通能源照明的数字孪生方案
大数据·数据库·人工智能·科普知识
深盾科技_Virbox3 小时前
软件授权工具静默安装实践
java·运维·数据库·安全·软件需求
延凡科技3 小时前
延凡科技综合监控预警处置平台—— 一体化视频AI安防闭环系统设计与功能实现[特殊字符]️
数据库·人工智能·科技·安全·能源