String的基本概念与不可变性- 创建
String对象的多种方式 String的常用方法详解String、StringBuilder和StringBuffer的区别与选择String在内存中的分配(常量池)- 格式化字符串
- 总结与最佳实践
String 的基本概念与不可变性
这是理解 String 的核心。

-
不可变性:
String对象一旦被创建,其内容(字符序列)就不能被改变,任何对String的修改操作(如concat(),replace(),substring()等)都不会修改原始对象,而是返回一个新的String对象。 -
为什么设计成不可变?
- 安全性: 在多线程环境下,不可变对象是线程安全的,因为它们不会被意外修改,将
String用作HashMap的键时,可以确保其哈希值不会改变。 - 性能优化: 由于
String不可变,JVM 可以对其进行优化,例如字符串常量池,这可以避免在内存中创建大量重复的字符串对象,节省内存空间。 - 哈希码缓存:
String类的hashCode()方法被重写了,并且在对象创建时计算并缓存了哈希码,因为字符串内容不变,所以它的哈希码也不变,这使得String非常适合作为哈希表的键。
- 安全性: 在多线程环境下,不可变对象是线程安全的,因为它们不会被意外修改,将
创建 String 对象的多种方式
主要有两种方式:字面量赋值和 new 关键字构造。
字面量赋值
这是最常用、最推荐的方式。

String str1 = "Hello, World!"; String str2 = "Hello, World!"; // str1 和 str2 指向内存中的同一个对象(在常量池中)
使用 new 关键字
这种方式会在堆内存中创建一个新的 String 对象,即使内容相同,也会创建新的对象。
String str3 = new String("Hello, World!");
String str4 = new String("Hello, World!"); // str3 和 str4 指向堆内存中两个不同的对象
混合使用的情况
String s1 = "hello";
String s2 = new String("hello");
String s3 = "hello";
System.out.println(s1 == s2); // false (s1在常量池,s2在堆)
System.out.println(s1 == s3); // true (s1和s3都指向常量池中的同一个对象)
String 的常用方法详解
1 长度与访问
-
length(): 返回字符串的长度(字符数)。String str = "Java"; System.out.println(str.length()); // 输出 4
-
charAt(int index): 返回指定索引处的字符。String str = "Java"; System.out.println(str.charAt(0)); // 输出 'J'
-
toCharArray(): 将字符串转换为字符数组。
(图片来源网络,侵删)String str = "Java"; char[] chars = str.toCharArray(); // chars = ['J', 'a', 'v', 'a']
2 查找与判断
-
contains(CharSequence s): 判断字符串是否包含指定的字符序列。String str = "I love Java"; System.out.println(str.contains("Java")); // true -
startsWith(String prefix)/endsWith(String suffix): 判断字符串是否以指定前缀/后缀开始或结束。String str = "filename.txt"; System.out.println(str.startsWith("file")); // true System.out.println(str.endsWith(".txt")); // true -
indexOf(String str)/lastIndexOf(String str): 返回指定子字符串第一次/最后一次出现的索引,如果找不到,返回-1。String str = "hello world, hello java"; System.out.println(str.indexOf("hello")); // 输出 0 System.out.println(str.lastIndexOf("hello")); // 输出 13 -
isEmpty(): 判断字符串是否为空(长度为0)。System.out.println("".isEmpty()); // true System.out.println(" ".isEmpty()); // false
3 转换与修改(注意:都返回新对象)
-
substring(int beginIndex)/substring(int beginIndex, int endIndex): 截取子字符串。String str = "Hello, World!"; System.out.println(str.substring(7)); // 输出 "World!" System.out.println(str.substring(7, 12)); // 输出 "World" (不包含endIndex)
-
concat(String str): 连接两个字符串。 运算符底层也调用此方法。String s1 = "Hello"; String s2 = s1.concat(", World!"); System.out.println(s2); // 输出 "Hello, World!" -
replace(char oldChar, char newChar)/replace(CharSequence target, CharSequence replacement): 替换字符或字符序列。String str = "apple"; System.out.println(str.replace('a', 'A')); // 输出 "Apple" System.out.println(str.replace("app", "or")); // 输出 "orange" -
toLowerCase()/toUpperCase(): 转换为小写/大写。String str = "Java"; System.out.println(str.toLowerCase()); // 输出 "java" System.out.println(str.toUpperCase()); // 输出 "JAVA"
-
trim(): 去除字符串首尾的空白字符(空格、制表符、换行等)。String str = " Hello Java "; System.out.println(str.trim()); // 输出 "Hello Java"
4 比较与分割
-
equals(Object anObject): 比较两个字符串的内容是否完全相同。String s1 = "hello"; String s2 = "Hello"; System.out.println(s1.equals(s2)); // false
-
equalsIgnoreCase(String anotherString): 忽略大小写比较内容。String s1 = "hello"; String s2 = "HELLO"; System.out.println(s1.equalsIgnoreCase(s2)); // true
-
compareTo(String anotherString): 字典序比较,返回一个整数值:0: 两个字符串相等。< 0: 调用字符串小于参数字符串。> 0: 调用字符串大于参数字符串。System.out.println("apple".compareTo("banana")); // 负数,因为 'a' < 'b' System.out.println("banana".compareTo("apple")); // 正数,因为 'b' > 'a'
-
split(String regex): 根据正则表达式分割字符串,返回一个字符串数组。String str = "apple,banana,orange"; String[] fruits = str.split(","); // fruits = ["apple", "banana", "orange"]
String、StringBuilder 和 StringBuffer 的区别与选择
| 特性 | String |
StringBuilder |
StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 线程安全 (不可变) | 非线程安全 | 线程安全 |
| 性能 | 频繁修改时性能差 | 性能最好 | 性能比 StringBuilder 稍差 |
| 适用场景 | 存储不变的字符串常量 | 单线程环境下进行大量字符串拼接操作 | 多线程环境下进行大量字符串拼接操作 |
如何选择?
-
如果字符串内容不需要改变:始终使用
String。String name = "John Doe";
-
如果在单线程环境下需要频繁修改字符串内容(如循环拼接):使用
StringBuilder,因为它最快。StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append(i); } String result = sb.toString(); -
如果在多线程环境下需要频繁修改字符串内容:使用
StringBuffer,它的方法是同步的(synchronized),可以保证线程安全。// 在多线程环境中,多个线程可能同时调用 append 方法 StringBuffer sb = new StringBuffer(); // ... (线程安全操作)
String 在内存中的分配(常量池)
这是 String 面试的高频考点。
-
字面量创建 ():
- 当使用字面量创建
String时,JVM 会首先在字符串常量池 中查找是否已经存在该值的字符串。 - 如果存在,直接返回常量池中该对象的引用。
- 如果不存在,则在常量池中创建一个新的字符串对象,并返回其引用。
- 目的:复用对象,节省内存。
- 当使用字面量创建
-
new关键字创建:- 使用
new关键字创建String对象时,无论常量池中是否存在相同值的字符串,都一定会在堆内存中创建一个新的String对象。 - 如果常量池中不存在该字符串,JVM 会同时在常量池中创建一个对象,然后在堆内存中也创建一个对象,堆中的对象会指向常量池中的对象。
- 目的:确保每次调用
new都得到一个全新的对象。
- 使用
// 示例分析
String s1 = "hello"; // 1. 在常量池中创建 "hello"
String s2 = "hello"; // 2. 从常量池中查找,直接引用 s1 的对象
System.out.println(s1 == s2); // true, 引用相同
String s3 = new String("hello"); // 3. 在堆中创建一个新的 "hello" 对象,常量池中已有
System.out.println(s1 == s3); // false, s1在常量池,s3在堆
String s4 = new String("hello") + " world"; // 4. 先在堆中创建 "hello",再拼接 " world",生成新对象 "hello world"
String s5 = "hello world"; // 5. 在常量池中创建 "hello world"
System.out.println(s4 == s5); // false, s4在堆,s5在常量池
格式化字符串
String.format() 方法可以像 printf 一样格式化字符串,非常强大。
String name = "Alice";
int age = 30;
double score = 95.5;
// 使用 format 方法
String formattedStr = String.format("Name: %s, Age: %d, Score: %.2f", name, age, score);
System.out.println(formattedStr); // 输出: Name: Alice, Age: 30, Score: 95.50
// 常用格式说明符:
// %s: 字符串
// %d: 整数
// %f: 浮点数
// %.2f: 保留两位小数的浮点数
// %c: 字符
// %b: 布尔值
总结与最佳实践
- 优先使用字面量:对于不会改变的字符串,使用 赋值,利用常量池优化。
- 避免不必要的拼接:在循环中频繁使用 拼接字符串,会创建大量临时对象,性能很差,应使用
StringBuilder。 - 明确线程需求:在单线程中用
StringBuilder,在多线程中用StringBuffer。 - 善用
equals和equalsIgnoreCase时,使用equals,而不是 。 比较的是对象的内存地址。 - 理解不可变性:
String的修改操作都是返回新对象,这有助于理解代码行为和性能问题。
希望这份详细的总结能帮助你全面掌握 Java String 的用法!
