前言
从这篇开始进入Java 常用 API。这一篇关于两个最高频的类:String和StringBuilder。
String 是 Java 里用得最多的类,没有之一。期待和你的一起进步
一、String 的不可变性
1.1 什么是不可变性?
String 对象一旦创建,它的内容就不能被修改。
java
String s = "hello"; s = "world"; // 这不是修改,是重新指向了一个新对象
看起来s变了,但实际上:
原来的
"hello"对象还在,没变新的
"world"对象被创建s重新指向了"world"
1.2 为什么不可变?
看 String 的源码:
java
public final class String { private final char[] value; // JDK 8 // private final byte[] value; // JDK 9+ }两个关键点:
class 用 final 修饰:不能被继承,防止子类破坏不可变性
value 数组用 final 修饰:引用不能变,且 String 内部不提供修改数组的方法
1.3 代码验证
java
String s1 = "hello"; String s2 = s1; s1 = s1 + " world"; System.out.println(s1); // hello world System.out.println(s2); // hello
s1拼接后指向了新对象,s2还是指向原来的"hello",不受影响。
1.4 我的理解
String 就像刻在石头上的字。你想改,改不了,只能重新刻一块新的石头,然后把牌子指过去。
核心一句话:String 内容不可变,任何"修改"都是创建新对象。
二、字符串常量池
2.1 什么是字符串常量池?
字符串常量池(String Pool)是 JVM 在堆内存中开辟的一块区域,专门用来存放字符串字面量。
2.2 两种创建方式的区别
java
String s1 = "hello"; // 直接赋值,走常量池 String s2 = new String("hello"); // new 创建,走堆内存内存结构:
text
栈内存 常量池(堆中) 堆内存 +------+ +------------+ +----------------+ | s1 | --> | "hello" | <----| new String 对象 | +------+ +------------+ +----------------+ | s2 | ----------------------->| value -> "hello"| +------+ +----------------+
2.3 代码验证
java
String s1 = "hello"; String s2 = "hello"; String s3 = new String("hello"); System.out.println(s1 == s2); // true,都指向常量池同一个对象 System.out.println(s1 == s3); // false,s3 是堆里的新对象2.4 常量池的好处
节省内存:相同字面量只存一份
提高效率:直接复用,不用重新创建
2.5 我的理解
常量池就像公共图书馆。你说"我要《如何让富婆爱上我》这本书",管理员先看看书架上有没有,有就直接给你(复用),没有才去买一本放上去。
new String("hello")就像你自己复印了一本,虽然内容一样,但和图书馆那本不是同一本。
核心一句话:字面量走常量池,new 走堆内存。
三、String 的 equals 与 == 的区别
3.1 区别
| 对比项 | == | equals |
|---|---|---|
| 比较内容 | 地址(是不是同一个对象) | 内容(字符是否相同) |
| 所属 | 运算符 | Object 类的方法 |
| String 中 | 比较地址 | 重写后比较内容 |
3.2 代码示例
java
String s1 = "hello"; String s2 = "hello"; String s3 = new String("hello"); System.out.println(s1 == s2); // true,同一对象 System.out.println(s1 == s3); // false,不同对象 System.out.println(s1.equals(s3)); // true,内容相同3.3 equals 源码
java
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String) anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }先比地址,地址相同直接返回 true;地址不同再逐个字符比较。
3.4 我的理解
==就是"是不是同一本书",equals就是"内容是不是一样"。
你和同学各拿一本《Java 入门》,==问的是"你俩拿的是不是同一本",equals问的是"你俩书的内容是不是一样"。
核心一句话:== 比地址,equals 比内容,字符串比较一律用 equals。
四、String 的构造方法
4.1 常用构造方法
| 构造方法 | 说明 |
|---|---|
String() | 空字符串 |
String(String original) | 复制一个字符串 |
String(char[] value) | 用字符数组创建 |
String(char[] value, int offset, int count) | 用字符数组的一部分创建 |
String(byte[] bytes) | 用字节数组创建(默认平台编码) |
String(byte[] bytes, Charset charset) | 用字节数组 + 指定编码创建 |
4.2 代码示例
java
// 空字符串 String s1 = new String(); System.out.println(s1); // "" // 复制 String s2 = new String("hello"); System.out.println(s2); // hello // 字符数组 char[] chars = {'h', 'e', 'l', 'l', 'o'}; String s3 = new String(chars); System.out.println(s3); // hello // 字符数组的一部分 String s4 = new String(chars, 1, 3); System.out.println(s4); // ell // 字节数组 byte[] bytes = {104, 101, 108, 108, 111}; String s5 = new String(bytes); System.out.println(s5); // hello4.3 我的理解
构造方法就是给你多种方式去造一个 String。可以用字符数组造,可以用字节数组造,也可以复制一个现成的。
核心一句话:String 构造方法支持空串、复制、字符数组、字节数组等多种创建方式。
五、String 常用 API
5.1 判断类
| 方法 | 说明 |
|---|---|
equals(Object obj) | 比较内容 |
equalsIgnoreCase(String str) | 忽略大小写比较 |
contains(String str) | 是否包含子串 |
startsWith(String prefix) | 是否以某前缀开头 |
endsWith(String suffix) | 是否以某后缀结尾 |
isEmpty() | 是否为空字符串 |
5.2 获取类
| 方法 | 说明 |
|---|---|
length() | 长度 |
charAt(int index) | 获取指定位置字符 |
indexOf(String str) | 第一次出现的索引 |
lastIndexOf(String str) | 最后一次出现的索引 |
substring(int begin) | 从 begin 截到末尾 |
substring(int begin, int end) | 截取 [begin, end) |
5.3 转换类
| 方法 | 说明 |
|---|---|
toCharArray() | 转字符数组 |
getBytes() | 转字节数组 |
toLowerCase() | 转小写 |
toUpperCase() | 转大写 |
trim() | 去掉首尾空白 |
replace(CharSequence, CharSequence) | 替换 |
5.4 分割与拼接
| 方法 | 说明 |
|---|---|
split(String regex) | 按正则分割 |
join(CharSequence, CharSequence...) | 静态方法,拼接 |
5.5 代码示例
java
String s = " Hello, World "; System.out.println(s.length()); // 16 System.out.println(s.trim()); // "Hello, World" System.out.println(s.trim().toLowerCase()); // "hello, world" System.out.println(s.contains("World")); // true System.out.println(s.trim().charAt(0)); // 'H' System.out.println(s.trim().substring(0, 5));// "Hello" System.out.println(s.trim().indexOf("o")); // 4 String[] parts = "a,b,c".split(","); System.out.println(Arrays.toString(parts)); // [a, b, c] String joined = String.join("-", "a", "b", "c"); System.out.println(joined); // a-b-c5.6 我的理解
String 的 API 分四类:判断、获取、转换、分割拼接。不用全背,记住常用的就行,其余的用的时候查文档。
核心一句话:String API 分判断、获取、转换、分割拼接四类。
六、StringBuilder 的作用和意义
6.1 为什么需要 StringBuilder?
String 是不可变的,每次拼接都会创建新对象:
java
String s = ""; for (int i = 0; i < 1000; i++) { s += i; // 每次循环都创建新 String 对象 }上面这段代码,循环 1000 次,创建了 1000 个 String 对象,效率极低。
用 StringBuilder:
java
StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append(i); // 始终操作同一个对象 } String s = sb.toString();6.2 StringBuilder 的特点
可变:内部维护一个 char 数组,可以动态扩容
高效:拼接时不会创建新对象
非线程安全:多线程环境下可能出问题
6.3 我的理解
String 像刻石头,改一次就得重新刻一块。StringBuilder 像白板,可以反复擦写,效率高得多。
核心一句话:StringBuilder 可变,适合频繁拼接;String 不可变,适合少量固定字符串。
七、StringBuilder 常用方法
7.1 常用 API
| 方法 | 说明 |
|---|---|
append(...) | 追加内容,返回自身 |
insert(int, ...) | 在指定位置插入 |
delete(int start, int end) | 删除 [start, end) |
deleteCharAt(int index) | 删除指定位置字符 |
replace(int start, int end, String str) | 替换 |
reverse() | 反转 |
toString() | 转成 String |
length() | 长度 |
charAt(int index) | 获取指定位置字符 |
7.2 代码示例
java
StringBuilder sb = new StringBuilder("hello"); sb.append(" world"); // hello world sb.insert(0, "say: "); // say: hello world sb.delete(0, 5); // hello world sb.replace(0, 5, "HELLO"); // HELLO world sb.reverse(); // dlrow OLLEH System.out.println(sb.toString());7.3 链式调用
append、insert、delete等方法都返回this,所以可以链式调用:
java
String result = new StringBuilder() .append("Hello") .append(", ") .append("World") .append("!") .toString(); System.out.println(result); // Hello, World!7.4 我的理解
StringBuilder 的方法大多返回自身,所以能像流水线一样一直点下去,写起来很顺。
核心一句话:StringBuilder 方法返回自身,支持链式调用。
八、StringBuilder vs StringBuffer
8.1 区别
| 对比项 | StringBuilder | StringBuffer |
|---|---|---|
| 线程安全 | ❌ 不安全 | ✅ 安全 |
| 效率 | 高 | 低 |
| 版本 | JDK 1.5 | JDK 1.0 |
| 适用场景 | 单线程 | 多线程 |
8.2 为什么 StringBuffer 线程安全?
StringBuffer 的方法大多用synchronized修饰:
java
public synchronized StringBuffer append(String str) { // ... }加锁保证同一时刻只有一个线程能操作,所以安全,但效率降低。
8.3 如何选择?
单线程:用 StringBuilder(绝大多数情况)
多线程:用 StringBuffer
8.4 我的理解
StringBuilder 像一个人用白板,随便写,快。StringBuffer 像多人共用白板,得排队,一次只能一个人写,安全但慢。
实际开发中,绝大多数场景是单线程,所以StringBuilder 用得更多。
核心一句话:单线程用 StringBuilder,多线程用 StringBuffer。
九、核心口诀
| 知识点 | 口诀 |
|---|---|
| String 不可变 | 内容不可改,改就创建新对象 |
| 字符串常量池 | 字面量走常量池,new 走堆 |
| equals vs == | == 比地址,equals 比内容 |
| String 构造 | 空串、复制、字符数组、字节数组 |
| String API | 判断、获取、转换、分割拼接 |
| StringBuilder | 可变,适合频繁拼接 |
| StringBuilder API | append/insert/delete/reverse,支持链式 |
| StringBuilder vs StringBuffer | 单线程 StringBuilder,多线程 StringBuffer |
十、常见易错点
| 易错点 | 说明 |
|---|---|
| 用 == 比较字符串内容 | 必须用 equals,== 比的是地址 |
| 循环里用 String 拼接 | 每次创建新对象,效率极低,改用 StringBuilder |
| 混淆常量池和堆 | 字面量在常量池,new 在堆 |
| StringBuilder 当线程安全用 | StringBuilder 非线程安全,多线程用 StringBuffer |
| 忘记 toString() | StringBuilder 转 String 必须调 toString() |
| substring 越界 | end 不能超过 length,且 begin ≤ end |