🚀 Java String类完全指南
String是Java的灵魂 - 无论你写什么Java程序,都离不开字符串处理。本教程将带你深入理解String的一切!
📖 目录
🔍 String基础概念
什么是String?
String 类是 Java 中最常用的类之一,它表示字符序列。字符串对象是不可变的,也就是说,一旦创建了字符串对象,就不能再改变它的内容。
String greeting = "Hello, World!"; // 字符串字面量
String name = new String("张三"); // 通过构造器创建
String为什么不可变?
🧱 核心特性:String的不可变性(Immutable)
String对象一旦被创建,它的值就不能被改变。每一个看起来修改字符串的操作,实际上都是创建并返回了一个全新的String对象,原来的字符串纹丝不动。
这就像是在石碑上刻字 🪦,刻好了就不能修改。如果你想得到不同的内容,只能重新刻一块新石碑。
✨ 不可变性的好处
- 🔒 线程安全:多线程环境下无需同步
- 🚀 性能优化:可以使用字符串常量池
- 🛡️ 安全性:可用作HashMap的key,不会被恶意修改
- 💾 缓存友好:hashCode可以缓存,提高HashMap性能
String源码解析
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
// Java 8及之前:使用char数组存储
private final char value[];
// Java 9及之后:使用byte数组存储,更节省内存
private final byte[] value;
private final byte coder; // 编码标识:LATIN1或UTF16
// 缓存hashCode
private int hash; // Default to 0
}
🔍 真正不可变的原因
❌ 常见误解:final修饰数组不是String不可变的根本原因
✅ 真正原因:
- 字符数组被
final修饰且为private- 外部无法直接访问 - String类没有提供修改数组的方法 - 没有setter方法
- String类被
final修饰 - 无法被子类破坏不可变性 - 所有"修改"方法都返回新对象 - 如substring、replace等
💡 理解要点:
final修饰引用类型变量时,只是引用地址不能改变,但对象内容是可以改变的。String不可变是因为精心设计的封装,而不是简单的final关键字。
⚔️ 三剑客对比:String vs StringBuilder vs StringBuffer
📊 核心差异对比表
| 特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 🚫 不可变 | ✅ 可变 | ✅ 可变 |
| 线程安全 | ✅ 线程安全 | ❌ 不安全 | ✅ 线程安全 |
| 性能 | 🐌 慢(频繁创建对象) | 🚀 最快 | ⚡ 较快(有同步开销) |
| 使用场景 | 少量数据、常量 | 单线程大量操作 | 多线程大量操作 |
🔧 实现原理
Java 8及之前
// String - 不可变,char数组
private final char value[];
// StringBuilder/StringBuffer - 可变,char数组
char[] value; // 没有final修饰
// StringBuffer通过synchronized保证线程安全
public synchronized StringBuffer append(String str) {
super.append(str);
return this;
}
Java 9及之后
// String - 优化内存使用,byte数组 + coder标识
private final byte[] value;
private final byte coder; // LATIN1=0, UTF16=1
// 更节省内存,特别是对于纯Latin1字符的字符串
🚀 性能对比实例
public class StringPerformanceTest {
private static final int COUNT = 100000;
public static void main(String[] args) {
// 测试String拼接(极慢!)
long start = System.currentTimeMillis();
String str = "";
for (int i = 0; i < COUNT; i++) {
str += "a"; // 每次都创建新对象!
}
System.out.println("String拼接耗时: " + (System.currentTimeMillis() - start) + "ms");
// 测试StringBuilder(最快)
start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < COUNT; i++) {
sb.append("a");
}
String result = sb.toString();
System.out.println("StringBuilder耗时: " + (System.currentTimeMillis() - start) + "ms");
// 测试StringBuffer(较快)
start = System.currentTimeMillis();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < COUNT; i++) {
buffer.append("a");
}
String result2 = buffer.toString();
System.out.println("StringBuffer耗时: " + (System.currentTimeMillis() - start) + "ms");
}
}
💡 使用建议总结
🎯 选择标准
// 1. 少量数据操作 - 使用String
String message = "Hello, " + name + "!"; // 编译器会优化
// 2. 单线程大量操作 - 使用StringBuilder
StringBuilder sql = new StringBuilder();
sql.append("SELECT * FROM users WHERE name = '")
.append(name)
.append("' AND age > ")
.append(age);
// 3. 多线程环境 - 使用StringBuffer
StringBuffer logBuffer = new StringBuffer();
// 多个线程可以安全地调用logBuffer.append()
⚠️ 重要性能提示
// ❌ 性能灾难:循环中的字符串拼接
public String badConcatenation(String[] items) {
String result = "";
for (String item : items) {
result += item + ","; // 每次循环都创建新String对象!
}
return result;
}
// ✅ 性能优化:使用StringBuilder
public String goodConcatenation(String[] items) {
StringBuilder sb = new StringBuilder();
for (String item : items) {
sb.append(item).append(",");
}
return sb.toString();
}
// ✅ Java 8+ Stream方式(函数式风格)
public String functionalConcatenation(String[] items) {
return String.join(",", items); // 最简洁!
}
🏊♂️ 字符串常量池深度解析
什么是字符串常量池?
字符串常量池是JVM为了提高性能和减少内存开销而设计的一块特殊内存区域,用于存储字符串字面量和运行时通过intern()方法加入的字符串。
🔍 内存分配分析
// 场景1:字面量创建 - 直接在常量池中
String str1 = "Hello"; // 常量池中创建"Hello"
String str2 = "Hello"; // 复用常量池中的"Hello"
System.out.println(str1 == str2); // true
// 场景2:new创建 - 堆中创建,但内容引用常量池
String str3 = new String("Hello"); // 堆中创建对象,内容指向常量池
System.out.println(str1 == str3); // false
// 场景3:编译期优化
String str4 = "Hel" + "lo"; // 编译期优化为"Hello"
System.out.println(str1 == str4); // true
// 场景4:运行期拼接
String str5 = "Hel";
String str6 = str5 + "lo"; // 运行期拼接,在堆中创建新对象
System.out.println(str1 == str6); // false
🧠 常量池工作原理
intern()方法详解
intern()方法是理解常量池的关键:
public class InternExample {
public static void main(String[] args) {
// 示例1:堆中字符串调用intern()
String s1 = new String("hello1");
String s2 = "hello1";
System.out.println(s1 == s2); // false - s1在堆中,s2在常量池
System.out.println(s1.intern() == s2); // true - intern()返回常量池引用
System.out.println(s1.intern() == s1); // false - s1在堆中
// 示例2:常量池字符串调用intern()
String s3 = "hello2";
System.out.println(s3.intern() == s3); // true - 本身就在常量池中
// 示例3:intern的实际应用
String[] data = {"Java", "Python", "Java", "C++", "Java"};
// 使用intern减少重复字符串
Set<String> uniqueStrings = new HashSet<>();
for (String str : data) {
uniqueStrings.add(str.intern()); // 相同内容只存储一份
}
System.out.println("去重后大小: " + uniqueStrings.size()); // 3
}
}
💾 intern()方法的作用机制
- 如果常量池中存在:直接返回常量池中的引用
- 如果常量池中不存在:将该字符串添加到常量池,并返回引用
- 内存优化:特别适合处理大量重复字符串的场景
🔧 String方法大全 - 超实用宝典
📊 信息查询方法
这些方法用于获取字符串的基本信息,就像检查字符串的"体检报告":
| 方法 | 作用 | 示例 & 结果 |
|---|---|---|
length() | 获取字符串长度 | "Hello".length() → 5 |
charAt(int index) | 获取指定位置字符 | "Hello".charAt(1) → 'e' |
isEmpty() | 判断是否为空字符串 | "".isEmpty() → true |
isBlank() | 判断是否为空白字符串 | " ".isBlank() → true |
codePointAt(int index) | 获取指定位置Unicode码点 | "Java".codePointAt(0) → 74 |
public class StringInfoDemo {
public static void main(String[] args) {
String text = " Hello, World! ";
System.out.println("长度: " + text.length()); // 16
System.out.println("第1个字符: " + text.charAt(0)); // ' '
System.out.println("是否为空: " + text.isEmpty()); // false
System.out.println("是否为空白: " + text.isBlank()); // false
String empty = "";
String blank = " ";
System.out.println("empty.isEmpty(): " + empty.isEmpty()); // true
System.out.println("blank.isEmpty(): " + blank.isEmpty()); // false
System.out.println("blank.isBlank(): " + blank.isBlank()); // true
}
}
⚖️ 比较判断方法
这些方法用于字符串的比较和内容判断:
| 方法 | 作用 | 示例 & 结果 |
|---|---|---|
equals(Object obj) | 比较内容是否相等(强烈推荐) | "Hi".equals("Hi") → true |
equalsIgnoreCase(String str) | 忽略大小写比较内容 | "Hi".equalsIgnoreCase("hI") → true |
compareTo(String str) | 字典序比较,返回差值 | "ABC".compareTo("ABD") → -1 |
compareToIgnoreCase(String str) | 忽略大小写字典序比较 | "abc".compareToIgnoreCase("ABC") → 0 |
contains(CharSequence s) | 判断是否包含指定子串 | "Hello".contains("ell") → true |
startsWith(String prefix) | 判断是否以指定前缀开头 | "Hello".startsWith("He") → true |
endsWith(String suffix) | 判断是否以指定后缀结尾 | "Hello".endsWith("lo") → true |
matches(String regex) | 判断是否匹配正则表达式 | "abc123".matches("[a-z]+\\d+") → true |
public class StringCompareDemo {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "hello";
String str3 = "Hello";
// equals vs == 重要区别!
System.out.println("str1 == str3: " + (str1 == str3)); // true (常量池)
System.out.println("str1.equals(str2): " + str1.equals(str2)); // false (区分大小写)
System.out.println("str1.equalsIgnoreCase(str2): " + str1.equalsIgnoreCase(str2)); // true
// compareTo字典序比较
System.out.println("'Apple' vs 'Banana': " + "Apple".compareTo("Banana")); // -1
System.out.println("'Zebra' vs 'Apple': " + "Zebra".compareTo("Apple")); // 25
// 实用判断方法
String email = "user@example.com";
System.out.println("是否包含@: " + email.contains("@")); // true
System.out.println("是否以.com结尾: " + email.endsWith(".com")); // true
System.out.println("是否以user开头: " + email.startsWith("user")); // true
// 正则表达式匹配
String phone = "123-456-7890";
System.out.println("是否匹配电话格式: " + phone.matches("\\d{3}-\\d{3}-\\d{4}")); // true
}
}
🔄 转换操作方法
这些方法会生成新的字符串(因为String不可变):
| 方法 | 作用 | 示例 & 结果 |
|---|---|---|
substring(int begin) | 截取子串(从指定位置到结尾) | "Hello".substring(2) → "llo" |
substring(int begin, int end) | 截取子串(左闭右开区间) | "Hello".substring(1, 4) → "ell" |
concat(String str) | 拼接字符串 | "Hello".concat(" World") → "Hello World" |
replace(char old, char new) | 替换所有指定字符 | "Hello".replace('l', 'p') → "Heppo" |
replace(CharSequence old, CharSequence new) | 替换所有指定子串 | "Hi Hi".replace("Hi", "Bye") → "Bye Bye" |
replaceAll(String regex, String replacement) | 按正则表达式替换 | "a1b2c3".replaceAll("\\d", "#") → "a#b#c#" |
replaceFirst(String regex, String replacement) | 替换第一个匹配 | "abac".replaceFirst("a", "X") → "Xbac" |
toLowerCase() | 转换为小写 | "Hello".toLowerCase() → "hello" |
toUpperCase() | 转换为大写 | "Hello".toUpperCase() → "HELLO" |
trim() | 去除首尾空白字符 | " Hi ".trim() → "Hi" |
strip() | 去除首尾空白(Unicode支持) | " Hi ".strip() → "Hi" |
stripLeading() | 去除开头空白 | " Hi ".stripLeading() → "Hi " |
stripTrailing() | 去除结尾空白 | " Hi ".stripTrailing() → " Hi" |
repeat(int count) | 重复字符串 | "Hi".repeat(3) → "HiHiHi" |
public class StringTransformDemo {
public static void main(String[] args) {
String text = " Hello, World! ";
// 基本转换操作
System.out.println("原字符串: '" + text + "'");
System.out.println("去除空白: '" + text.strip() + "'");
System.out.println("去除开头空白: '" + text.stripLeading() + "'");
System.out.println("去除结尾空白: '" + text.stripTrailing() + "'");
// 大小写转换
String sentence = "Hello, Java Programming!";
System.out.println("转小写: " + sentence.toLowerCase());
System.out.println("转大写: " + sentence.toUpperCase());
// 字符串截取
String url = "https://www.example.com/path/to/page";
System.out.println("协议: " + url.substring(0, url.indexOf("://")));
System.out.println("域名: " + url.substring(url.indexOf("://") + 3, url.indexOf("/", 8)));
// 替换操作
String template = "Dear {name}, your order {orderId} is ready.";
String personalized = template.replace("{name}", "张三")
.replace("{orderId}", "ORD-001");
System.out.println("个性化后: " + personalized);
// 正则表达式替换
String messyText = "Hello123World456Java789";
String cleanText = messyText.replaceAll("\\d+", ""); // 移除所有数字
System.out.println("清理后: " + cleanText);
// 重复操作(Java 11+)
String separator = "-".repeat(20);
System.out.println(separator);
// 实用示例:格式化用户输入
String userInput = " admin@EXAMPLE.COM ";
String normalized = userInput.strip().toLowerCase();
System.out.println("标准化邮箱: " + normalized);
}
}
🔄 分割连接方法
这些方法是处理字符串的"瑞士军刀":
| 方法 | 作用 | 示例 & 结果 |
|---|---|---|
toCharArray() | 转换为字符数组 | "Hi".toCharArray() → ['H', 'i'] |
getBytes() | 转换为字节数组 | "Hi".getBytes() → [72, 105] |
split(String regex) | 按正则表达式分割 | "A,B,C".split(",") → ["A","B","C"] |
join(CharSequence delimiter, CharSequence... elements) | 用分隔符连接字符串 | String.join("-", "A","B","C") → "A-B-C" |
lines() | 按行分割(Java 11+) | "A\nB\nC".lines() → Stream["A","B","C"] |
indent(int n) | 缩进处理(Java 12+) | "A".indent(2) → " A\n" |
transform(Function f) | 函数式转换(Java 12+) | "123".transform(Integer::parseInt) → 123 |
public class StringSplitJoinDemo {
public static void main(String[] args) {
// split() 方法详解
String csv = "张三,25,北京;李四,30,上海;王五,28,广州";
// 按分号分割,然后再按逗号分割
String[] users = csv.split(";");
for (String user : users) {
String[] details = user.split(",");
System.out.println("姓名: " + details[0] + ", 年龄: " + details[1] + ", 城市: " + details[2]);
}
// 正则表达式分割(高级用法)
String messyData = "apple123banana456orange789";
String[] fruits = messyData.split("\\d+"); // 按数字分割
System.out.println("提取的水果: " + Arrays.toString(fruits));
// join() 方法详解
String[] words = {"Java", "is", "awesome"};
String sentence1 = String.join(" ", words); // 用空格连接
String sentence2 = String.join("-", words); // 用连字符连接
String sentence3 = String.join(", ", "张三", "李四", "王五"); // 可变参数
System.out.println("空格连接: " + sentence1);
System.out.println("连字符连接: " + sentence2);
System.out.println("逗号连接: " + sentence3);
// 实际应用:CSV处理
String[] headers = {"姓名", "年龄", "邮箱"};
String[] values = {"张三", "25", "zhangsan@example.com"};
String csvLine = String.join(",", values);
System.out.println("CSV行: " + csvLine);
// 数组转换
String text = "Hello";
char[] chars = text.toCharArray();
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
System.out.println("字符数组: " + Arrays.toString(chars));
System.out.println("字节数组: " + Arrays.toString(bytes));
// Java 11+ 新方法示例
String multiLine = "第一行\n第二行\n第三行";
multiLine.lines().forEach(System.out::println);
// Java 12+ transform示例
String number = "42";
Integer parsed = number.transform(Integer::parseInt);
System.out.println("转换后的数字: " + parsed);
System.out.println("数字平方: " + parsed.transform(n -> n * n));
}
}
⚡ 性能优化秘籍
🚄 字符串拼接性能对比
public class StringConcatPerformance {
private static final int COUNT = 100000;
public static void main(String[] args) {
// 测试1: + 操作符(最差!)
long start = System.currentTimeMillis();
String result1 = "";
for (int i = 0; i < COUNT; i++) {
result1 += "a"; // 每次都创建新对象!
}
long time1 = System.currentTimeMillis() - start;
// 测试2: concat()方法(差)
start = System.currentTimeMillis();
String result2 = "";
for (int i = 0; i < COUNT; i++) {
result2 = result2.concat("a");
}
long time2 = System.currentTimeMillis() - start;
// 测试3: StringBuilder(好)
start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < COUNT; i++) {
sb.append("a");
}
String result3 = sb.toString();
long time3 = System.currentTimeMillis() - start;
// 测试4: StringBuffer(较好)
start = System.currentTimeMillis();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < COUNT; i++) {
buffer.append("a");
}
String result4 = buffer.toString();
long time4 = System.currentTimeMillis() - start;
// 测试5: String.join(简洁)
String[] array = new String[COUNT];
Arrays.fill(array, "a");
start = System.currentTimeMillis();
String result5 = String.join("", array);
long time5 = System.currentTimeMillis() - start;
System.out.println("拼接次数: " + COUNT);
System.out.println(" + 操作符: " + time1 + "ms (最差)");
System.out.println(" concat(): " + time2 + "ms (差)");
System.out.println("StringBuilder: " + time3 + "ms (推荐)");
System.out.println("StringBuffer: " + time4 + "ms (线程安全)");
System.out.println("String.join: " + time5 + "ms (简洁)");
}
}
💾 内存优化技巧
1. 使用常量池优化内存
public class MemoryOptimization {
private static final String COMMON_PREFIX = "PREFIX_";
private static final String COMMON_SUFFIX = "_SUFFIX";
public static void main(String[] args) {
// ❌ 内存浪费:每次都创建新的字符串对象
for (int i = 0; i < 10000; i++) {
String bad = "PREFIX_" + i + "_SUFFIX"; // 创建大量临时对象
}
// ✅ 内存优化:使用StringBuilder和常量
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.setLength(0); // 重置长度,复用StringBuilder
sb.append(COMMON_PREFIX).append(i).append(COMMON_SUFFIX);
String good = sb.toString();
}
// ✅ 进一步优化:使用intern()方法
Set<String> uniqueStrings = new HashSet<>();
for (int i = 0; i < 10000; i++) {
String str = "VALUE_" + (i % 100); // 只有100种不同值
uniqueStrings.add(str.intern()); // 相同内容只存储一份
}
System.out.println("去重后字符串数量: " + uniqueStrings.size());
}
}
2. 避免不必要的字符串操作
public class StringOptimizationTips {
// ✅ 好的做法:避免重复计算
public static void goodExample(String text) {
if (text == null || text.isEmpty()) {
return; // 先检查空值,避免后续操作
}
// 缓存计算结果
String upperText = text.toUpperCase();
String lowerText = text.toLowerCase();
// 复用计算结果
System.out.println("大写: " + upperText);
System.out.println("小写: " + lowerText);
System.out.println("长度: " + text.length()); // length()很轻量
}
// ❌ 坏的做法:重复计算和创建临时对象
public static void badExample(String text) {
System.out.println("大写: " + text.toUpperCase()); // 计算1次
System.out.println("小写: " + text.toLowerCase()); // 计算1次
System.out.println("再次大写: " + text.toUpperCase()); // 又计算1次!
// 不必要的字符串拼接
String temp = text + " processed";
temp = temp.trim();
temp = temp.toLowerCase();
}
}
🔧 StringBuilder性能优化
public class StringBuilderOptimization {
// ✅ 优化1: 预估容量
public static void optimizedConcatenation(String[] items) {
// 预估总长度,避免扩容
int estimatedLength = Arrays.stream(items)
.mapToInt(String::length)
.sum() + items.length - 1; // 加上分隔符的长度
StringBuilder sb = new StringBuilder(estimatedLength);
for (int i = 0; i < items.length; i++) {
if (i > 0) sb.append(",");
sb.append(items[i]);
}
return sb.toString();
}
// ✅ 优化2: 链式调用
public static String chainedOperations(String name, int age, String city) {
return new StringBuilder(50) // 预估容量
.append("姓名: ").append(name)
.append(", 年龄: ").append(age)
.append(", 城市: ").append(city)
.toString();
}
// ✅ 优化3: 复用StringBuilder(线程不安全环境)
private static final ThreadLocal<StringBuilder> SB_POOL =
ThreadLocal.withInitial(() -> new StringBuilder(256));
public static String formatUserInfo(String name, int age) {
StringBuilder sb = SB_POOL.get();
sb.setLength(0); // 重置长度但保留容量
return sb.append("User: ").append(name).append(", Age: ").append(age).toString();
}
}
🎯 实战应用场景
1. 📝 文本处理工具类
public class TextUtils {
/**
* 清理用户输入:去除多余空格,统一格式
*/
public static String normalizeInput(String input) {
if (input == null) return "";
return input.strip() // 去除首尾空白
.replaceAll("\\s+", " ") // 多个空格合并为一个
.toLowerCase(); // 转小写(根据需求)
}
/**
* 驼峰命名转换:user_name -> userName
*/
public static String toCamelCase(String snakeCase) {
if (snakeCase == null || snakeCase.isEmpty()) {
return snakeCase;
}
StringBuilder result = new StringBuilder();
boolean capitalizeNext = false;
for (char c : snakeCase.toCharArray()) {
if (c == '_') {
capitalizeNext = true;
} else {
if (capitalizeNext) {
result.append(Character.toUpperCase(c));
capitalizeNext = false;
} else {
result.append(Character.toLowerCase(c));
}
}
}
return result.toString();
}
/**
* 生成文件扩展名
*/
public static String getFileExtension(String filename) {
if (filename == null || filename.isEmpty()) {
return "";
}
int lastDot = filename.lastIndexOf('.');
return lastDot == -1 ? "" : filename.substring(lastDot + 1);
}
/**
* 生成随机字符串
*/
public static String generateRandomString(int length, String chars) {
SecureRandom random = new SecureRandom();
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append(chars.charAt(random.nextInt(chars.length())));
}
return sb.toString();
}
/**
* 掩码处理:隐藏敏感信息
*/
public static String maskSensitiveInfo(String info, int visibleChars) {
if (info == null || info.length() <= visibleChars) {
return info;
}
int maskLength = info.length() - visibleChars;
String masked = "*".repeat(maskLength);
String visible = info.substring(info.length() - visibleChars);
return masked + visible;
}
}
2. 🌐 URL和路径处理
public class UrlPathUtils {
/**
* URL参数解析
*/
public static Map<String, String> parseQueryParams(String query) {
Map<String, String> params = new HashMap<>();
if (query == null || query.isEmpty()) {
return params;
}
String[] pairs = query.split("&");
for (String pair : pairs) {
String[] keyValue = pair.split("=", 2);
if (keyValue.length == 2) {
try {
String key = URLDecoder.decode(keyValue[0], StandardCharsets.UTF_8);
String value = URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8);
params.put(key, value);
} catch (Exception e) {
// 忽略解码错误
}
}
}
return params;
}
/**
* 构建URL参数
*/
public static String buildQueryParams(Map<String, String> params) {
if (params == null || params.isEmpty()) {
return "";
}
StringBuilder query = new StringBuilder();
try {
for (Map.Entry<String, String> entry : params.entrySet()) {
if (query.length() > 0) {
query.append("&");
}
query.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8))
.append("=")
.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
}
} catch (Exception e) {
throw new RuntimeException("编码URL参数失败", e);
}
return query.toString();
}
/**
* 规范化路径:处理多个斜杠,确保以斜杠开头
*/
public static String normalizePath(String path) {
if (path == null) return "/";
return "/" + path.strip().replaceAll("/+", "/");
}
}
3. 📊 数据验证和格式化
public class ValidationUtils {
// 预编译正则表达式,提高性能
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");
private static final Pattern PHONE_PATTERN =
Pattern.compile("^1[3-9]\\d{9}$");
private static final Pattern ID_CARD_PATTERN =
Pattern.compile("^\\d{17}[\\dXx]$");
/**
* 验证邮箱格式
*/
public static boolean isValidEmail(String email) {
return email != null && EMAIL_PATTERN.matcher(email).matches();
}
/**
* 验证手机号格式(中国)
*/
public static boolean isValidPhone(String phone) {
return phone != null && PHONE_PATTERN.matcher(phone).matches();
}
/**
* 格式化银行卡号
*/
public static String formatBankCard(String cardNumber) {
if (cardNumber == null || cardNumber.isEmpty()) {
return "";
}
// 移除非数字字符
String digits = cardNumber.replaceAll("\\D", "");
// 每4位添加空格
StringBuilder formatted = new StringBuilder();
for (int i = 0; i < digits.length(); i++) {
if (i > 0 && i % 4 == 0) {
formatted.append(" ");
}
formatted.append(digits.charAt(i));
}
return formatted.toString();
}
/**
* 高亮搜索关键词
*/
public static String highlightKeyword(String text, String keyword, String highlightTag) {
if (text == null || keyword == null || keyword.isEmpty()) {
return text;
}
return text.replaceAll(
Pattern.quote(keyword),
highlightTag + keyword + highlightTag.replaceFirst("<", "</")
);
}
}
intern方法
public static void main(String args[]){
String s1=new String("hello1");
System.out.println(s1.intern()==s1);//false
System.out.println(s1.intern()=="hello1");//true
String s2="hello2";
System.out.println(s2.intern()==s2);//true
}
1、执行intern方法时,如果常量池中存在和String对象相同的字符串,则返回常量池中对应字符串的引用;
2、如果常量池中不存在对应的字符串,则添加该字符串到常量中,并返回字符串引用;
🧰 String 类方法大全(超实用清单)
我把常用方法分成了几大类,方便你查阅和理解。
1. 获取信息 ℹ️ (查询)
这些方法就像是在检查字符串的“体检报告”。
| 方法 | 作用 | 例子 & 输出 |
|---|---|---|
length() | 获取字符串的长度 | "Hello".length() -> 5 |
charAt(int index) | 获取指定索引位置的字符 | "Hello".charAt(1) -> 'e' |
indexOf(String str) | 返回子串第一次出现的索引 | "Hello".indexOf("l") -> 2 |
lastIndexOf(String str) | 返回子串最后一次出现的索引 | "Hello".lastIndexOf("l") -> 3 |
isEmpty() | 判断字符串是否为空(length() == 0) | "".isEmpty() -> true |
isBlank() | 判断字符串是否为空白(空格、制表符等) | " ".isBlank() -> true |
2. 比较与判断 ⚖️
这些方法用来比较字符串的内容。
| 方法 | 作用 | 例子 & 输出 |
|---|---|---|
equals(Object obj) | 比较内容是否相等(强烈推荐用这个!) | "Hi".equals("Hi") -> true |
equalsIgnoreCase(String str) | 忽略大小写,比较内容是否相等 | "Hi".equalsIgnoreCase("hI") -> true |
contains(CharSequence s) | 判断是否包含指定的子串 | "Hello".contains("ell") -> true |
startsWith(String prefix) | 判断是否以指定前缀开头 | "Hello".startsWith("He") -> true |
endsWith(String suffix) | 判断是否以指定后缀结尾 | "Hello".endsWith("lo") -> true |
3. 操作与变换 🛠️ (生成新字符串)
这些方法会“创造”出新的字符串。
| 方法 | 作用 | 例子 & 输出 |
|---|---|---|
substring(int beginIndex) | 截取子串(从开始索引到结尾) | "Hello".substring(2) -> "llo" |
substring(int begin, int end) | 截取子串([开始索引, 结束索引) 左闭右开) | "Hello".substring(1, 4) -> "ell" |
concat(String str) | 拼接字符串 | "Hello".concat(" World!") -> "Hello World!" |
replace(char old, char new) | 替换所有出现的指定字符 | "Hello".replace('l', 'p') -> "Heppo" |
replace(CharSequence old, CharSequence new) | 替换所有出现的指定序列 | "Hi there!".replace("Hi", "Bye") -> "Bye there!" |
toLowerCase() | 转换为小写 | "Hello".toLowerCase() -> "hello" |
toUpperCase() | 转换为大写 | "Hello".toUpperCase() -> "HELLO" |
trim() | 去除字符串首尾的空白字符 | " Hi ".trim() -> "Hi" |
strip() | 去除字符串首尾的空白字符(功能比trim更强,支持Unicode) | " Hi ".strip() -> "Hi" |
4. 转换与拆分 🧩
这些方法是处理字符串的“瑞士军刀”。
| 方法 | 作用 | 例子 & 输出 |
|---|---|---|
toCharArray() | 将字符串转换为字符数组 char[] | "Hi".toCharArray() -> ['H', 'i'] |
split(String regex) | 根据正则表达式分割字符串(超级常用!) | 见下方详解 👇 |
join(CharSequence delimiter, CharSequence... elements) | 用指定的分隔符连接多个字符串 | String.join("-", "A", "B", "C") -> "A-B-C" |
🎯 实际操作演示
1. 如何分割字符串?split() 🔪
这是处理文本数据(如CSV文件)的利器。
String data = "Apple,Banana,Orange,Watermelon";
// 使用逗号分割字符串,得到一个字符串数组
String[] fruits = data.split(",");
for (String fruit : fruits) {
System.out.println(fruit);
}
// 输出:
// Apple
// Banana
// Orange
// Watermelon
// 高级用法:使用正则表达式
String sentence = "Hello World! How are you?";
// 按空格、感叹号、问号分割
String[] words = sentence.split("\\s+|!|\\?");
for (String word : words) {
System.out.println(word);
}
// 输出:
// Hello
// World
// How
// are
// you
2. 如何匹配与查找?matches() & contains() 🔍
// 1. 判断是否完全匹配一个正则表达式(规则)
String email = "user@example.com";
boolean isValidEmail = email.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");
System.out.println("Is valid email? " + isValidEmail); // 输出: true or false
// 2. 判断是否包含简单子串
String message = "Welcome to Java programming!";
boolean hasJava = message.contains("Java");
System.out.println("Contains 'Java'? " + hasJava); // 输出: true
3. 综合小例子:处理用户输入 🎮
String userInput = " , apple, BANANA; Orange ";
// 处理流程:1.去首尾空格 2.统一分隔符 3.按新分隔符分割 4.转换为小写并去除元素首尾空格
String[] cleanedFruits = userInput
.trim() // "apple, BANANA; Orange"
.replace(';', ',') // 将分号统一替换为逗号 "apple, BANANA, Orange"
.split("\\s*,\\s*"); // 按“逗号+任意空格”分割
for (String fruit : cleanedFruits) {
System.out.println(fruit.toLowerCase().strip()); // 转换为小写并去除可能残留的空格
}
// 输出:
// apple
// banana
// orange
❓ 常见面试题 - 必考点精析
🎯 面试题1:String不可变性理解
问题:为什么String是不可变的?有什么好处?
回答思路:
- 安全性:防止恶意修改,适合作为HashMap的key
- 线程安全:多线程环境下无需同步
- 缓存hashCode:提高HashMap性能
- 字符串常量池:节省内存,提高性能
// 面试官追问演示
public class StringImmutabilityDemo {
public static void main(String[] args) {
String str1 = "hello";
String str2 = str1; // 两个引用指向同一个对象
str1 = str1 + " world"; // 看起来修改了str1,实际上创建了新对象
System.out.println(str1); // "hello world"
System.out.println(str2); // "hello" - 原对象没有被改变!
System.out.println(str1 == str2); // false - 确实是不同对象
}
}
🎯 面试题2:equals() vs == 深度对比
经典问题:何时用equals(),何时用==?
答案:
- ==:比较对象地址(是否同一个对象)
- equals():比较字符串内容(是否相同的字符序列)
public class EqualsVsEquals {
public static void main(String[] args) {
String str1 = "hello"; // 常量池
String str2 = "hello"; // 复用常量池
String str3 = new String("hello"); // 堆中新对象
// == 比较:地址比较
System.out.println(str1 == str2); // true - 同一个常量池对象
System.out.println(str1 == str3); // false - 不同地址
// equals() 比较:内容比较
System.out.println(str1.equals(str2)); // true - 内容相同
System.out.println(str1.equals(str3)); // true - 内容相同
// 重要:比较字符串内容永远用equals()!
String userInput = "hello";
if (userInput.equals("hello")) {
System.out.println("正确做法:使用equals()");
}
// 坏做法:使用==比较
if (userInput == "hello") { // 这种写法可能出错!
System.out.println("危险做法:使用==");
}
}
}
🎯 面试题3:常量池与intern()机制
高频率问题:解释字符串常量池的工作原理
核心要点:
- 编译期优化:字符串字面量拼接会被优化
- 运行期处理:new出来的对象在堆中
- intern()方法:手动将字符串加入常量池
public class ConstantPoolInterview {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = s1 + s2; // 运行期拼接
System.out.println(s3 == s4); // false - s4在堆中
// 使用intern()
String s5 = (s1 + s2).intern();
System.out.println(s3 == s5); // true - intern()返回常量池引用
// 高级例子:intern的实际应用
String[] bigData = generateLargeDataSet(); // 10000个字符串
Set<String> uniqueStrings = new HashSet<>();
for (String str : bigData) {
uniqueStrings.add(str.intern()); // 大幅减少内存使用
}
}
private static String[] generateLargeDataSet() {
String[] data = new String[10000];
for (int i = 0; i < data.length; i++) {
data[i] = "VALUE_" + (i % 100); // 只有100种不同值
}
return data;
}
}
🎯 面试题4:StringBuilder vs StringBuffer vs String
性能问题:三者的选择和使用场景
回答要点:
| 场景 | 推荐 | 原因 |
|---|---|---|
| 少量拼接 | String + StringBuilder | 编译器优化,代码简洁 |
| 单线程大量操作 | StringBuilder | 性能最佳,无线程安全开销 |
| 多线程环境 | StringBuffer | 线程安全,同步开销 |
| 循环中拼接 | StringBuilder | 避免大量临时对象 |
public class PerformanceComparison {
public static void main(String[] args) {
// 面试时的性能对比演示
demonstratePerformance();
}
private static void demonstratePerformance() {
int iterations = 50000;
// String拼接 - 性能最差
long start = System.currentTimeMillis();
String result1 = "";
for (int i = 0; i < iterations; i++) {
result1 += i;
}
long time1 = System.currentTimeMillis() - start;
// StringBuilder - 性能最佳
start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < iterations; i++) {
sb.append(i);
}
String result2 = sb.toString();
long time2 = System.currentTimeMillis() - start;
System.out.println("String拼接: " + time1 + "ms (性能灾难!)");
System.out.println("StringBuilder: " + time2 + "ms (性能最优)");
System.out.println("性能提升: " + (time1 / (double) time2) + "倍");
}
}
🚨 常见陷阱与避坑指南
⚠️ 陷阱1:循环中的字符串拼接
// ❌ 性能灾难:每次循环都创建新对象
public String badConcatenation(List<String> items) {
String result = "";
for (String item : items) {
result += item + ","; // 每次都new String()!
}
return result;
}
// ✅ 正确做法:使用StringBuilder
public String goodConcatenation(List<String> items) {
StringBuilder sb = new StringBuilder();
for (String item : items) {
sb.append(item).append(",");
}
return sb.toString();
}
⚠️ 陷阱2:混淆 == 和 equals()
// ❌ 坏做法:用 == 比较内容
public boolean validatePassword(String input) {
String correctPassword = "password123";
return input == correctPassword; // 错误!应该用equals()
}
// ✅ 正确做法:用 equals() 比较内容
public boolean validatePasswordCorrect(String input) {
String correctPassword = "password123";
// 先检查null,避免NPE
return input != null && input.equals(correctPassword);
}
// ✅ 更好的做法:把常量放前面,避免NPE
public boolean validatePasswordBest(String input) {
return "password123".equals(input); // 常量.equals(变量),永远安全
}
⚠️ 陷阱3:忽略null检查
// ❌ 可能抛出NullPointerException
public String processString(String input) {
return input.strip().toLowerCase(); // 如果input为null就崩溃了!
}
// ✅ 正确做法:添加null检查
public String processStringSafely(String input) {
if (input == null || input.isEmpty()) {
return ""; // 返回默认值
}
return input.strip().toLowerCase();
}
// ✅ 函数式风格:使用Optional
public String processStringFunctional(String input) {
return Optional.ofNullable(input)
.map(String::strip)
.map(String::toLowerCase)
.orElse("");
}
⚠️ 陷阱4:split()方法的正则表达式陷阱
// ❌ 错误:没有转义正则表达式特殊字符
public void splitBadExample() {
String data = "file.txt";
String[] parts = data.split("."); // .在正则中表示"任意字符"!
// 结果:parts是空数组!
}
// ✅ 正确:转义特殊字符
public void splitCorrectExample() {
String data = "file.txt";
String[] parts = data.split("\\."); // 转义.表示真正的点号
// 结果:parts = ["file", "txt"]
// 更安全的方式
String[] parts2 = data.split(Pattern.quote(".")); // Pattern.quote()自动转义
}
⚠️ 陷阱5:忽略字符编码
// ❌ 错误:使用平台默认编码
public byte[] convertToBytesBad(String text) {
return text.getBytes(); // 在不同系统上可能结果不同!
}
// ✅ 正确:明确指定编码
public byte[] convertToBytesGood(String text) {
try {
return text.getBytes("UTF-8"); // 明确指定UTF-8编码
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8编码不支持", e);
}
}
// ✅ 最佳方式:使用StandardCharsets(Java 7+)
public byte[] convertToBytesBest(String text) {
return text.getBytes(StandardCharsets.UTF_8); // 无异常,更简洁
}
⚠️ 陷阱6:不必要的字符串创建
// ❌ 浪费内存:重复创建相同的字符串
public void wasteMemory() {
for (int i = 0; i < 10000; i++) {
String cacheKey = "user_profile_" + i; // 每次都创建新String
String config = "default_config"; // 重复创建相同字符串
// 处理逻辑...
}
}
// ✅ 优化:使用常量和StringBuilder
public void optimizeMemory() {
final String CACHE_KEY_PREFIX = "user_profile_";
final String DEFAULT_CONFIG = "default_config";
StringBuilder sb = new StringBuilder(30); // 预估容量
for (int i = 0; i < 10000; i++) {
sb.setLength(0); // 重置,复用StringBuilder
sb.append(CACHE_KEY_PREFIX).append(i);
String cacheKey = sb.toString();
// 处理逻辑...
}
}
⚠️ 陷阱7:substring()方法的内存泄漏(Java 7之前)
// ⚠️ Java 7之前的问题:substring可能持有原字符串的完整引用
public String getShortNameBad(String longName) {
return longName.substring(0, 3); // 可能导致内存泄漏!
}
// ✅ Java 7+:substring()已经修复,会创建新的字符数组
public String getShortNameGood(String longName) {
return longName.substring(0, 3); // 安全,不会内存泄漏
}
// ✅ 兼容性做法:如果必须在旧版本Java中运行
public String getShortNameCompatible(String longName) {
return new String(longName.substring(0, 3)); // 强制创建新String
}
📚 学习总结
🎯 核心要点回顾
- String不可变性:线程安全,但每次修改都创建新对象
- 常量池机制:节省内存,提高性能
- 性能选择:
- 少量数据:String
- 单线程大量操作:StringBuilder
- 多线程:StringBuffer
- equals() vs ==:比较内容用equals(),比较地址用==
- intern()方法:手动管理常量池
🚀 最佳实践清单
- ✅ 永远用equals()比较字符串内容
- ✅ 循环中字符串拼接使用StringBuilder
- ✅ 添加null检查避免NPE
- ✅ 明确指定字符编码
- ✅ 使用StringBuilder预估容量
- ✅ split()时转义正则表达式特殊字符
- ✅ 考虑使用intern()优化重复字符串
🔥 进阶学习路径
- 深入JVM:学习字符串常量池的内存管理
- 性能调优:掌握字符串操作的性能分析
- 函数式编程:学习Java 8+的字符串处理新特性
- 设计模式:理解在大型项目中如何管理字符串
💡 最后建议:String是Java中使用最频繁的类之一,掌握它的特性对编写高质量Java代码至关重要。在实际开发中,要时刻注意性能和内存使用,避免常见的字符串操作陷阱。
🎉 恭喜!你已经掌握了Java String的核心知识! 现在去实践吧,记住理论只是基础,真正的掌握来自于大量编码经验的积累!