跳到主要内容

🚀 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不可变的根本原因

✅ 真正原因

  1. 字符数组被final修饰且为private - 外部无法直接访问
  2. String类没有提供修改数组的方法 - 没有setter方法
  3. String类被final修饰 - 无法被子类破坏不可变性
  4. 所有"修改"方法都返回新对象 - 如substring、replace等

💡 理解要点final修饰引用类型变量时,只是引用地址不能改变,但对象内容是可以改变的。String不可变是因为精心设计的封装,而不是简单的final关键字。


⚔️ 三剑客对比:String vs StringBuilder vs StringBuffer

📊 核心差异对比表

特性StringStringBuilderStringBuffer
可变性🚫 不可变✅ 可变✅ 可变
线程安全✅ 线程安全❌ 不安全✅ 线程安全
性能🐌 慢(频繁创建对象)🚀 最快⚡ 较快(有同步开销)
使用场景少量数据、常量单线程大量操作多线程大量操作

🔧 实现原理

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()方法的作用机制

  1. 如果常量池中存在:直接返回常量池中的引用
  2. 如果常量池中不存在:将该字符串添加到常量池,并返回引用
  3. 内存优化:特别适合处理大量重复字符串的场景

🔧 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是不可变的?有什么好处?

回答思路

  1. 安全性:防止恶意修改,适合作为HashMap的key
  2. 线程安全:多线程环境下无需同步
  3. 缓存hashCode:提高HashMap性能
  4. 字符串常量池:节省内存,提高性能
// 面试官追问演示
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()机制

高频率问题:解释字符串常量池的工作原理

核心要点

  1. 编译期优化:字符串字面量拼接会被优化
  2. 运行期处理:new出来的对象在堆中
  3. 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
}

📚 学习总结

🎯 核心要点回顾

  1. String不可变性:线程安全,但每次修改都创建新对象
  2. 常量池机制:节省内存,提高性能
  3. 性能选择
    • 少量数据:String
    • 单线程大量操作:StringBuilder
    • 多线程:StringBuffer
  4. equals() vs ==:比较内容用equals(),比较地址用==
  5. intern()方法:手动管理常量池

🚀 最佳实践清单

  • 永远用equals()比较字符串内容
  • 循环中字符串拼接使用StringBuilder
  • 添加null检查避免NPE
  • 明确指定字符编码
  • 使用StringBuilder预估容量
  • split()时转义正则表达式特殊字符
  • 考虑使用intern()优化重复字符串

🔥 进阶学习路径

  1. 深入JVM:学习字符串常量池的内存管理
  2. 性能调优:掌握字符串操作的性能分析
  3. 函数式编程:学习Java 8+的字符串处理新特性
  4. 设计模式:理解在大型项目中如何管理字符串

💡 最后建议:String是Java中使用最频繁的类之一,掌握它的特性对编写高质量Java代码至关重要。在实际开发中,要时刻注意性能和内存使用,避免常见的字符串操作陷阱。

🎉 恭喜!你已经掌握了Java String的核心知识! 现在去实践吧,记住理论只是基础,真正的掌握来自于大量编码经验的积累!