Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java章节编写规范
  • Java基础

    • Java概述
    • Java语法
    • 面向对象编程
    • Java数组
    • String字符串
      • 1. String 的基本特性
      • 2. String 为什么不可变
      • 3. String 内部结构
      • 4. 字符串创建方式
        • 4.1 字面量
        • 4.2 new String
      • 5. 字符串常量池
      • 6. intern 方法
      • 7. 字符串比较
        • 7.1 ==
        • 7.2 equals
        • 7.3 compareTo
      • 8. 常用 API
      • 9. replace、replaceAll 与 split
      • 10. 字符串拼接
        • 10.1 少量拼接
        • 10.2 循环拼接
      • 11. String、StringBuilder、StringBuffer
      • 12. 字符编码与乱码
      • 13. 文本块
      • 14. 常见开发问题
        • 14.1 空字符串和 null
        • 14.2 脱敏和截断
        • 14.3 String 作为锁对象
      • Tips 快问快答
    • 异常处理
    • 基本类型与包装类
    • Object类与常用方法
    • 泛型
    • 枚举与注解
    • 反射
    • 值传递与引用
    • Lambda表达式与函数式接口
    • Stream API
    • Optional与空值处理
    • 日期时间API
    • Java现代语法特性
  • Java集合

  • Java并发

  • Java IO

  • JVM

  • Java
  • Java基础
Wray
2026-06-24
目录

String字符串

String 是 Java 中使用频率最高的类之一。它既是日常业务开发里的文本类型,也是理解对象不可变性、常量池、编码、性能优化和面试题的入口。

掌握 String 时,不能只停留在 length()、substring()、replace() 这些 API。更重要的是理解:

  • 字符串为什么不可变。
  • 字符串字面量和 new String() 有什么区别。
  • 字符串常量池如何减少重复对象。
  • == 和 equals 为什么结果不同。
  • 拼接、循环拼接、StringBuilder 的性能差异。
  • 字符编码和乱码问题如何产生。

# 1. String 的基本特性

特性 说明
引用类型 String 是类,不是基本类型
不可变 创建后内容不能修改
可共享 字符串字面量会进入字符串常量池
实现了 Comparable 可以按字典序比较
实现了 CharSequence 可作为字符序列使用
final 类 不能被继承

示例:

String name = "Java";

name 是引用变量,"Java" 是字符串对象。

# 2. String 为什么不可变

不可变指字符串对象内部保存的字符内容不能被改变。

String s = "Java";
s = s + "!";

这段代码不是修改原来的 "Java",而是创建了新字符串 "Java!",再让变量 s 指向新对象。

初始:
s ───────────▶ "Java"

拼接后:
              "Java"
s ───────────▶ "Java!"

不可变设计的价值:

价值 说明
安全 文件路径、类名、网络地址等字符串不容易被篡改
线程安全 多线程共享同一个字符串不需要加锁
支持常量池 相同字面量可以共享对象
支持 hash 缓存 String 可安全作为 HashMap key
简化设计 调用方不必担心传入字符串被方法内部修改

如果字符串可变,那么下面代码就会很危险:

Map<String, String> map = new HashMap<>();
String key = "user:1";
map.put(key, "Tom");

// 如果 key 内容能变,HashMap 就可能再也找不到原来的键

# 3. String 内部结构

不同 JDK 版本中 String 的内部存储有所变化:

版本 典型存储方式 说明
JDK 8 char[] 每个字符通常占 2 字节
JDK 9+ byte[] + coder Compact Strings,Latin-1 字符可节省空间

可以概念化理解为:

String 对象
┌────────────────────┐
│ hash               │
│ coder              │ JDK 9+
│ value ─────────┐   │
└────────────────┼───┘
                 ▼
          byte[] / char[]
          ┌──────────────┐
          │ 字符内容      │
          └──────────────┘

注意:内部字段是实现细节,业务代码不应该依赖它。理解内部结构主要是为了知道字符串不可变、内存占用和编码转换的成本。

# 4. 字符串创建方式

# 4.1 字面量

String a = "hello";
String b = "hello";
System.out.println(a == b); // true

字面量 "hello" 会放入字符串常量池,a 和 b 指向同一个池中对象。

a ─────┐
       ▼
    "hello"  字符串常量池
       ▲
b ─────┘

# 4.2 new String

String a = "hello";
String b = new String("hello");

System.out.println(a == b);      // false
System.out.println(a.equals(b)); // true

new String("hello") 会创建新的堆对象,变量 b 指向这个新对象。

字符串常量池
┌──────────┐
│ "hello"  │◀──────── a
└──────────┘

堆内存
┌──────────┐
│ "hello"  │◀──────── b
└──────────┘

开发中通常不应该写 new String("xxx"),它会制造不必要的对象。

# 5. 字符串常量池

字符串常量池用于复用相同内容的字符串字面量。

String a = "java";
String b = "ja" + "va";

System.out.println(a == b); // true

因为 "ja" + "va" 是编译期常量,编译器会直接优化成 "java"。

但如果有变量参与:

String part = "ja";
String a = "java";
String b = part + "va";

System.out.println(a == b); // false

part + "va" 是运行期拼接,默认会创建新对象。

执行链路:

编译期常量拼接
"ja" + "va"
    │
    ▼
"java" 直接进入常量池

运行期变量拼接
part + "va"
    │
    ▼
运行时创建新字符串对象

# 6. intern 方法

intern() 会尝试把字符串内容放入常量池,并返回池中对应字符串的引用。

String a = new String("hello");
String b = a.intern();
String c = "hello";

System.out.println(b == c); // true

实际开发中不建议随意使用 intern():

  • 它会增加常量池管理成本。
  • 大量动态字符串进入池中可能增加内存压力。
  • 通常业务代码通过普通字符串复用和缓存就足够了。

intern() 更适合对高重复率、生命周期较长的字符串做有意识的优化。

# 7. 字符串比较

# 7.1 ==

== 比较两个引用是否指向同一个对象。

String a = "java";
String b = new String("java");

System.out.println(a == b); // false

# 7.2 equals

equals() 比较字符串内容。

System.out.println(a.equals(b)); // true

为了避免空指针,可以把常量放左边:

if ("ACTIVE".equals(status)) {
    // ...
}

或者使用:

Objects.equals(a, b);

# 7.3 compareTo

compareTo() 按字典序比较:

"abc".compareTo("abd"); // 小于 0
"abc".compareTo("abc"); // 等于 0
"abd".compareTo("abc"); // 大于 0

它常用于排序:

List<String> names = new ArrayList<>(List.of("Tom", "Alice"));
Collections.sort(names);

# 8. 常用 API

方法 作用
length() 字符串长度
isEmpty() 长度是否为 0
isBlank() 是否为空或全空白,Java 11+
charAt(int) 获取指定位置字符
substring 截取子串
contains 是否包含子串
startsWith 是否以某内容开头
endsWith 是否以某内容结尾
indexOf 查找位置
replace 普通替换
replaceAll 正则替换
split 正则切分
trim 去除首尾小于等于空格的字符
strip Unicode 感知的首尾空白去除,Java 11+

示例:

String text = "  Java Basic  ";

System.out.println(text.trim());       // "Java Basic"
System.out.println(text.contains("Java")); // true
System.out.println(text.substring(2, 6));  // "Java"

substring(begin, end) 是左闭右开区间:

"abcdef"
 012345

substring(1, 4) => "bcd"

# 9. replace、replaceAll 与 split

replace 是普通字符或字符串替换:

"a.b.c".replace(".", "-"); // "a-b-c"

replaceAll 使用正则表达式:

"a.b.c".replaceAll(".", "-"); // "-----"

因为正则里的 . 表示任意字符。若要替换普通点号:

"a.b.c".replaceAll("\\.", "-"); // "a-b-c"

split 也使用正则:

"a.b.c".split(".");    // 结果可能不是你想要的
"a.b.c".split("\\.");  // ["a", "b", "c"]

开发中如果只是普通替换,优先使用 replace,避免无意引入正则语义。

# 10. 字符串拼接

# 10.1 少量拼接

String message = "Hello " + name + "!";

少量拼接用 + 可读性好,编译器和运行时会做优化。

# 10.2 循环拼接

不推荐:

String result = "";
for (int i = 0; i < 10000; i++) {
    result += i;
}

推荐:

StringBuilder builder = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    builder.append(i);
}
String result = builder.toString();

循环拼接问题:

第 1 次:"" + "1"       -> 新字符串
第 2 次:"1" + "2"      -> 新字符串
第 3 次:"12" + "3"     -> 新字符串
...

中间对象多,性能和内存都不理想。

# 11. String、StringBuilder、StringBuffer

类 是否可变 线程安全 适用场景
String 不可变 安全 少量文本、常量、Map key
StringBuilder 可变 不安全 单线程频繁拼接
StringBuffer 可变 安全 老代码或确实需要同步的拼接

大多数业务代码中,循环拼接优先使用 StringBuilder。

构造 StringBuilder 时可以预估容量:

StringBuilder builder = new StringBuilder(1024);

如果能预估最终字符串长度,提前设置容量可以减少扩容复制。

# 12. 字符编码与乱码

字符串本身表示 Unicode 字符序列。只有当字符串和字节数组互相转换时,才涉及字符编码。

String text = "你好";
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
String decoded = new String(bytes, StandardCharsets.UTF_8);

编码转换流程:

String 字符
   │ encode UTF-8
   ▼
byte[]
   │ decode UTF-8
   ▼
String 字符

乱码通常来自编码和解码使用了不同字符集:

原始字符串 "你好"
   │ UTF-8 编码
   ▼
字节数组
   │ GBK 解码
   ▼
乱码

开发建议:

  • 明确指定字符集,不依赖平台默认编码。
  • 文件、网络、数据库、HTTP 响应统一使用 UTF-8。
  • 使用 StandardCharsets.UTF_8,避免手写 "UTF-8"。

# 13. 文本块

文本块适合多行字符串:

String sql = """
        SELECT id, name
        FROM user
        WHERE status = 'ACTIVE'
        """;

适用场景:

  • 多行 SQL。
  • JSON 示例。
  • HTML 模板片段。
  • 测试用大段文本。

注意:文本块也会处理缩进和换行,写测试断言时要注意实际字符串内容。

# 14. 常见开发问题

# 14.1 空字符串和 null

值 含义
null 没有引用任何字符串对象
"" 长度为 0 的字符串对象
" " 长度为 1,内容是空格

判断示例:

if (text == null || text.isBlank()) {
    // 空或空白
}

# 14.2 脱敏和截断

处理用户输入时,要注意字符串长度可能不是视觉字符数量。Emoji、组合字符、多字节字符都可能让“字符数量”问题变复杂。

普通业务校验可以用 length();涉及短信长度、数据库字节长度、前端显示宽度时,要按具体规则处理。

# 14.3 String 作为锁对象

不要使用字符串字面量作为锁:

synchronized ("LOCK") {
    // ...
}

字符串字面量可能被全局共享,容易和其他代码意外使用同一个锁。

推荐:

private final Object lock = new Object();

# Tips 快问快答

Q:String 是基本类型吗? A:不是。String 是 java.lang.String 类,是引用类型。

Q:字符串不可变是什么意思? A:字符串对象内容不能改变。变量可以重新指向新字符串,但原对象不变。

Q:String a = "x" 和 new String("x") 有什么区别? A:字面量优先使用常量池对象,new String 会额外创建堆对象。

Q:字符串常量池在 JDK 8 后位于元空间吗? A:不是。字符串对象本身在堆中,常量池相关结构不要简单理解成“字符串都在元空间”。

Q:为什么字符串比较要用 equals? A:== 比较引用地址,equals 比较字符内容。

Q:"a" + "b" == "ab" 为什么是 true? A:因为纯字面量拼接会在编译期优化成 "ab"。

Q:循环拼接为什么推荐 StringBuilder? A:它在同一个可变缓冲区中追加内容,减少中间字符串对象。

Q:StringBuffer 是不是一定比 StringBuilder 好? A:不是。StringBuffer 方法带同步开销,单线程拼接通常选 StringBuilder。

Q:replaceAll(".", "-") 为什么把所有字符都替换了? A:replaceAll 使用正则表达式,. 在正则里表示任意字符。

Q:乱码怎么排查? A:沿着“字符串 -> 字节 -> 字符串”的链路检查编码和解码字符集是否一致。

上次更新: 2026/06/24, 16:28:28
Java数组
异常处理

← Java数组 异常处理→

Copyright © 2023-2026 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式