在Java编程中,字符串是我们每天都要打交道的“老朋友”。无论是处理用户输入、操作文件内容,还是网络数据传输,字符串无处不在。今天,让我们深入探索Java字符串的奥秘,掌握这个看似简单却隐藏着无数技巧的数据类型。
引入:为什么字符串如此重要?
想象一下,你正在开发一个社交媒体应用:
- 用户发布的每一条动态都是字符串
- 用户之间的聊天消息是字符串
- 用户搜索的关键词也是字符串
在Java中,字符串不仅仅是字符的集合,它是一个功能强大的对象,提供了丰富的方法来帮助我们处理文本数据。据统计,Java程序中高达40%的对象都是String类型!
一、字符串基础:三种创建方式
1. 字符串的三种创建方式及区别
public class StringCreation {
public static void main(String[] args) {
System.out.println("=== 字符串的三种创建方式 ===\n");
// 方式1:直接赋值(最常用)
String str1 = "Hello, Java!";
System.out.println("方式1(直接赋值): " + str1);
// 方式2:使用new关键字
String str2 = new String("Hello, Java!");
System.out.println("方式2(new创建): " + str2);
// 方式3:从字符数组创建
char[] charArray = {'H', 'e', 'l', 'l', 'o'};
String str3 = new String(charArray);
System.out.println("方式3(字符数组): " + str3);
System.out.println("\n=== 重要区别:字符串常量池 ===");
// 比较两种创建方式的区别
String s1 = "Java"; // 存储在字符串常量池
String s2 = "Java"; // 从常量池中复用
String s3 = new String("Java"); // 在堆中新建对象
String s4 = new String("Java"); // 另一个新对象
System.out.println("s1 == s2 ? " + (s1 == s2)); // true,引用同一个对象
System.out.println("s1 == s3 ? " + (s1 == s3)); // false,不同对象
System.out.println("s3 == s4 ? " + (s3 == s4)); // false,两个不同的new对象
System.out.println("s1.equals(s3) ? " + s1.equals(s3)); // true,内容相同
System.out.println("\n=== 内存分析 ===");
System.out.println("直接赋值:检查常量池,有则复用,无则创建");
System.out.println("new创建:在堆中创建新对象,不去检查常量池");
// intern()方法:手动加入常量池
String s5 = new String("Python").intern();
String s6 = "Python";
System.out.println("s5 == s6 ? " + (s5 == s6)); // true,intern()后引用相同
}
}
二、字符串不可变性:理解字符串的本质
public class StringImmutability {
public static void main(String[] args) {
System.out.println("=== 字符串不可变性详解 ===\n");
// 什么是不可变性?
String original = "Hello";
System.out.println("原始字符串: " + original);
System.out.println("原始字符串哈希码: " + System.identityHashCode(original));
// 尝试"修改"字符串
String modified = original.concat(" World");
System.out.println("\n尝试修改后...");
System.out.println("修改后的字符串: " + modified);
System.out.println("修改后字符串哈希码: " + System.identityHashCode(modified));
System.out.println("原始字符串仍然是: " + original);
System.out.println("原始字符串哈希码: " + System.identityHashCode(original));
// 证明创建了新对象
System.out.println("\n哈希码不同,证明是不同对象!");
System.out.println("original == modified ? " + (original == modified));
// 多个"修改"操作的性能问题
System.out.println("\n=== 字符串拼接性能问题 ===");
String result = "";
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
result += i; // 每次循环都创建新字符串对象!
}
long endTime = System.currentTimeMillis();
System.out.println("使用+=拼接10000次耗时: " + (endTime - startTime) + "ms");
// 使用StringBuilder优化
StringBuilder sb = new StringBuilder();
startTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
String optimizedResult = sb.toString();
endTime = System.currentTimeMillis();
System.out.println("使用StringBuilder拼接10000次耗时: " + (endTime - startTime) + "ms");
System.out.println("性能提升: " + ((endTime - startTime) / 10) + "倍");
// 不可变性的好处
System.out.println("\n=== 字符串不可变性的好处 ===");
System.out.println("1. 线程安全:多个线程可以共享字符串而无需同步");
System.out.println("2. 缓存哈希码:哈希码只需计算一次");
System.out.println("3. 安全性:适合用作密码等敏感数据");
System.out.println("4. 常量池优化:字符串字面量可以被缓存和复用");
}
}
三、字符串常用操作方法大全
1. 字符串长度与空值检查
public class StringBasicOperations {
public static void main(String[] args) {
System.out.println("=== 字符串基础操作 ===\n");
String text = " Hello, Java World! ";
// 1. 获取长度
int length = text.length();
System.out.println("字符串长度: " + length);
// 2. 判断是否为空
System.out.println("是否为空: " + text.isEmpty());
System.out.println("是否为空白: " + text.isBlank()); // Java 11+
// 3. 去除空格
String trimmed = text.trim();
System.out.println("去除首尾空格: "" + trimmed + """);
// 4. 转换为大写/小写
System.out.println("大写: " + text.toUpperCase());
System.out.println("小写: " + text.toLowerCase());
// 5. 字符串比较
String str1 = "Java";
String str2 = "java";
String str3 = "Java";
System.out.println("\n=== 字符串比较 ===");
System.out.println("str1 == str3: " + (str1 == str3));
System.out.println("str1.equals(str3): " + str1.equals(str3));
System.out.println("str1.equals(str2): " + str1.equals(str2));
System.out.println("str1.equalsIgnoreCase(str2): " + str1.equalsIgnoreCase(str2));
System.out.println("str1.compareTo(str2): " + str1.compareTo(str2));
// 6. 字符串查找
System.out.println("\n=== 字符串查找 ===");
String searchText = "Java is fun. Java is powerful.";
System.out.println("是否包含"Java": " + searchText.contains("Java"));
System.out.println(""Java"首次出现位置: " + searchText.indexOf("Java"));
System.out.println(""Java"最后出现位置: " + searchText.lastIndexOf("Java"));
System.out.println("是否以"fun."结尾: " + searchText.endsWith("fun."));
System.out.println("是否以"Java"开头: " + searchText.startsWith("Java"));
// 7. 字符串提取
System.out.println("\n=== 字符串提取 ===");
System.out.println("提取子串(5-10): " + searchText.substring(5, 10));
System.out.println("提取从位置5开始: " + searchText.substring(5));
System.out.println("获取第0个字符: " + searchText.charAt(0));
// 8. 字符串替换
System.out.println("\n=== 字符串替换 ===");
System.out.println("替换所有"Java"为"Python": " +
searchText.replace("Java", "Python"));
System.out.println("替换首个"Java"为"Python": " +
searchText.replaceFirst("Java", "Python"));
System.out.println("正则表达式替换: " +
searchText.replaceAll("J\w+", "JavaScript"));
// 9. 字符串分割
System.out.println("\n=== 字符串分割 ===");
String csv = "Apple,Banana,Orange,Mango";
String[] fruits = csv.split(",");
System.out.println("分割结果:");
for (String fruit : fruits) {
System.out.println(" - " + fruit);
}
// 正则表达式分割
String complexText = "Java1Python2C++3JavaScript";
String[] langs = complexText.split("\d+");
System.out.println("\n正则分割结果:");
for (String lang : langs) {
System.out.println(" - " + lang);
}
}
}
2. 字符串格式化与转换
public class StringFormatting {
public static void main(String[] args) {
System.out.println("=== 字符串格式化 ===\n");
// 1. 传统格式化方式
String name = "张三";
int age = 25;
double salary = 8888.88;
String formatted1 = String.format("姓名: %s, 年龄: %d, 工资: %.2f", name, age, salary);
System.out.println("String.format(): " + formatted1);
// 2. 使用System.out.printf
System.out.printf("printf示例: 姓名: %s, 年龄: %d, 工资: %.2f\n", name, age, salary);
// 3. 各种格式说明符
System.out.println("\n=== 格式说明符示例 ===");
System.out.printf("整数: %d\n", 100);
System.out.printf("八进制: %o\n", 100);
System.out.printf("十六进制: %x\n", 100);
System.out.printf("科学计数法: %e\n", 1000.0);
System.out.printf("浮点数: %f\n", 3.14159);
System.out.printf("字符串: %s\n", "Hello");
System.out.printf("字符: %c\n", 'A');
System.out.printf("布尔值: %b\n", true);
System.out.printf("哈希码: %h\n", "Hello");
// 4. 宽度和对齐
System.out.println("\n=== 宽度和对齐 ===");
System.out.printf("左对齐: |%-10s|\n", "Java");
System.out.printf("右对齐: |%10s|\n", "Java");
System.out.printf("填充0: |%010d|\n", 123);
System.out.printf("千位分隔符: %,d\n", 1000000);
// 5. 日期时间格式化
System.out.println("\n=== 日期时间格式化 ===");
java.util.Date now = new java.util.Date();
System.out.printf("当前时间: %tT\n", now);
System.out.printf("当前日期: %tF\n", now);
System.out.printf("完整日期: %tc\n", now);
// 6. 其他转换方法
System.out.println("\n=== 其他转换方法 ===");
int number = 123;
System.out.println("int转String: " + String.valueOf(number));
System.out.println("int转String: " + Integer.toString(number));
String numStr = "456";
System.out.println("String转int: " + Integer.parseInt(numStr));
System.out.println("String转double: " + Double.parseDouble("3.14"));
// 7. 字符数组与字符串互转
char[] chars = {'H', 'e', 'l', 'l', 'o'};
String fromChars = new String(chars);
System.out.println("字符数组转String: " + fromChars);
char[] toChars = fromChars.toCharArray();
System.out.print("String转字符数组: ");
for (char c : toChars) {
System.out.print(c + " ");
}
System.out.println();
// 8. 字节数组与字符串互转(处理二进制数据)
String text = "Hello中文";
byte[] utf8Bytes = text.getBytes(java.nio.charset.StandardCharsets.UTF_8);
System.out.println("UTF-8字节数组长度: " + utf8Bytes.length);
String fromBytes = new String(utf8Bytes, java.nio.charset.StandardCharsets.UTF_8);
System.out.println("字节数组转回String: " + fromBytes);
}
}
四、StringBuilder和StringBuffer
public class StringBuilderDemo {
public static void main(String[] args) {
System.out.println("=== StringBuilder vs StringBuffer ===\n");
// 1. 基本使用
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
sb.append("!");
String result = sb.toString();
System.out.println("StringBuilder结果: " + result);
// 2. 链式调用
StringBuilder sb2 = new StringBuilder()
.append("Java")
.append(" ")
.append("is")
.append(" ")
.append("awesome!");
System.out.println("链式调用: " + sb2.toString());
// 3. 常用方法
StringBuilder text = new StringBuilder("HelloJava");
System.out.println("\n=== StringBuilder常用方法 ===");
System.out.println("原始: " + text);
// 插入
text.insert(5, " ");
System.out.println("在位置5插入空格: " + text);
// 删除
text.delete(5, 11);
System.out.println("删除Java: " + text);
// 替换
text.replace(0, 5, "Hi");
System.out.println("替换Hello为Hi: " + text);
// 反转
text.reverse();
System.out.println("反转: " + text);
// 设置长度
text.setLength(2);
System.out.println("设置长度为2: " + text);
// 4. StringBuffer(线程安全版本)
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("Thread Safe");
System.out.println("\nStringBuffer: " + stringBuffer.toString());
// 5. 性能对比
System.out.println("\n=== 性能对比 ===");
int iterations = 100000;
// String拼接
long start = System.currentTimeMillis();
String str = "";
for (int i = 0; i < iterations; i++) {
str += "a";
}
long stringTime = System.currentTimeMillis() - start;
// StringBuilder
start = System.currentTimeMillis();
StringBuilder sb3 = new StringBuilder();
for (int i = 0; i < iterations; i++) {
sb3.append("a");
}
String sbResult = sb3.toString();
long sbTime = System.currentTimeMillis() - start;
// StringBuffer
start = System.currentTimeMillis();
StringBuffer sb4 = new StringBuffer();
for (int i = 0; i < iterations; i++) {
sb4.append("a");
}
String bufferResult = sb4.toString();
long bufferTime = System.currentTimeMillis() - start;
System.out.println("String拼接耗时: " + stringTime + "ms");
System.out.println("StringBuilder耗时: " + sbTime + "ms");
System.out.println("StringBuffer耗时: " + bufferTime + "ms");
System.out.println("StringBuilder比String快 " + (stringTime/sbTime) + " 倍");
// 6. 使用场景建议
System.out.println("\n=== 使用场景建议 ===");
System.out.println("使用String:");
System.out.println(" • 字符串内容不经常改变");
System.out.println(" • 字符串常量");
System.out.println(" • 作为HashMap的键");
System.out.println("\n使用StringBuilder:");
System.out.println(" • 单线程环境下大量字符串拼接");
System.out.println(" • 循环中拼接字符串");
System.out.println(" • 需要高性能的字符串操作");
System.out.println("\n使用StringBuffer:");
System.out.println(" • 多线程环境下字符串操作");
System.out.println(" • 需要线程安全的字符串拼接");
}
}
五、正则表达式与字符串处理
import java.util.regex.*;
public class RegexDemo {
public static void main(String[] args) {
System.out.println("=== 正则表达式实战 ===\n");
// 1. 基本匹配
String text = "我的电话号码是138-1234-5678,邮箱是test@example.com";
System.out.println("原始文本: " + text);
System.out.println();
// 2. 查找电话号码
Pattern phonePattern = Pattern.compile("\d{3}-\d{4}-\d{4}");
Matcher phoneMatcher = phonePattern.matcher(text);
System.out.println("=== 查找电话号码 ===");
while (phoneMatcher.find()) {
System.out.println("找到电话: " + phoneMatcher.group());
System.out.println("开始位置: " + phoneMatcher.start());
System.out.println("结束位置: " + phoneMatcher.end());
}
// 3. 查找邮箱
Pattern emailPattern = Pattern.compile("\w+@\w+\.\w+");
Matcher emailMatcher = emailPattern.matcher(text);
System.out.println("\n=== 查找邮箱地址 ===");
if (emailMatcher.find()) {
System.out.println("找到邮箱: " + emailMatcher.group());
}
// 4. 替换操作
String replaced = text.replaceAll("\d{3}-\d{4}-\d{4}", "***-****-****");
System.out.println("\n=== 替换电话号码 ===");
System.out.println("替换后: " + replaced);
// 5. 分割字符串
String csv = "张三,25,北京;李四,30,上海;王五,28,广州";
String[] persons = csv.split("[;,]");
System.out.println("\n=== 分割复杂字符串 ===");
for (String part : persons) {
System.out.println("部分: " + part);
}
// 6. 验证输入格式
System.out.println("\n=== 输入验证 ===");
// 验证邮箱格式
String[] testEmails = {
"test@example.com",
"user.name@domain.co.uk",
"invalid-email",
"@nodomain.com",
"spaces in@email.com"
};
Pattern validEmailPattern = Pattern.compile("^[\w.-]+@[\w.-]+\.[a-zA-Z]{2,}$");
for (String email : testEmails) {
boolean isValid = validEmailPattern.matcher(email).matches();
System.out.println(email + " : " + (isValid ? "有效" : "无效"));
}
// 7. 提取分组
System.out.println("\n=== 提取分组信息 ===");
String log = "2024-01-15 14:30:25 [INFO] User login: user123";
Pattern logPattern = Pattern.compile("(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) \[(\w+)\] (.+)");
Matcher logMatcher = logPattern.matcher(log);
if (logMatcher.matches()) {
System.out.println("日期: " + logMatcher.group(1));
System.out.println("时间: " + logMatcher.group(2));
System.out.println("级别: " + logMatcher.group(3));
System.out.println("消息: " + logMatcher.group(4));
}
// 8. 常用正则表达式模式
System.out.println("\n=== 常用正则表达式 ===");
System.out.println("手机号: ^1[3-9]\d{9}$");
System.out.println("身份证: ^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9X]$");
System.out.println("IP地址: ^((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$");
System.out.println("URL: ^https?://[\w.-]+(\.[\w.-]+)+(/[\w.-]*)*$");
// 9. 性能优化建议
System.out.println("\n=== 正则表达式性能优化 ===");
System.out.println("1. 预编译Pattern对象(避免重复编译)");
System.out.println("2. 使用非贪婪匹配(.*?)避免回溯");
System.out.println("3. 尽量使用具体字符类而非通配符");
System.out.println("4. 使用边界匹配符(^和$)提高效率");
}
}
六、字符串性能优化与最佳实践
public class StringOptimization {
public static void main(String[] args) {
System.out.println("=== 字符串性能优化指南 ===\n");
// 1. 字符串拼接优化
System.out.println("=== 字符串拼接优化 ===");
// ❌ 不好的做法
System.out.println("❌ 不要在循环中使用+拼接字符串:");
String badResult = "";
for (int i = 0; i < 100; i++) {
badResult += "value" + i; // 每次循环都创建新StringBuilder和String对象
}
// ✅ 好的做法
System.out.println("✅ 使用StringBuilder:");
StringBuilder goodResult = new StringBuilder();
for (int i = 0; i < 100; i++) {
goodResult.append("value").append(i);
}
// 2. 预分配StringBuilder容量
System.out.println("\n=== StringBuilder容量预分配 ===");
// ❌ 不指定容量(默认16,频繁扩容)
StringBuilder sb1 = new StringBuilder();
// ✅ 预估计容量
int estimatedSize = 1000;
StringBuilder sb2 = new StringBuilder(estimatedSize);
System.out.println("预分配容量减少数组复制次数,提高性能");
// 3. 字符串比较优化
System.out.println("\n=== 字符串比较优化 ===");
String input = getUserInput(); // 假设从用户获取输入
// ❌ 不好的做法
if (input.equals("YES")) { // 可能NullPointerException
// ...
}
// ✅ 好的做法1:常量在前
if ("YES".equals(input)) { // 避免NullPointerException
System.out.println("常量在前,安全!");
}
// ✅ 好的做法2:使用equalsIgnoreCase忽略大小写
if ("yes".equalsIgnoreCase(input)) {
System.out.println("忽略大小写比较");
}
// 4. 字符串缓存优化
System.out.println("\n=== 字符串缓存策略 ===");
// 大量重复字符串时,使用intern()或自定义缓存
String[] names = new String[10000];
// ❌ 每次创建新对象
for (int i = 0; i < names.length; i++) {
names[i] = new String("CommonPrefix" + (i % 100));
}
// ✅ 使用intern()复用字符串
for (int i = 0; i < names.length; i++) {
names[i] = ("CommonPrefix" + (i % 100)).intern();
}
System.out.println("使用intern()减少内存占用");
// 5. 避免不必要的字符串操作
System.out.println("\n=== 避免不必要的操作 ===");
// ❌ 不必要的trim()
String userInput = "username";
if (userInput.trim().equals("username")) { // trim()创建新字符串
// ...
}
// ✅ 先检查是否需要trim
if (userInput.equals("username") || userInput.trim().equals("username")) {
System.out.println("减少不必要的trim()调用");
}
// 6. 使用StringJoiner(Java 8+)
System.out.println("\n=== 使用StringJoiner ===");
// 传统方式
StringBuilder traditional = new StringBuilder();
String[] items = {"Java", "Python", "JavaScript"};
for (int i = 0; i < items.length; i++) {
traditional.append(items[i]);
if (i < items.length - 1) {
traditional.append(", ");
}
}
System.out.println("传统方式: " + traditional);
// 使用StringJoiner
java.util.StringJoiner joiner = new java.util.StringJoiner(", ", "[", "]");
for (String item : items) {
joiner.add(item);
}
System.out.println("StringJoiner: " + joiner.toString());
// 7. 大文本处理优化
System.out.println("\n=== 大文本处理 ===");
// 使用字符流处理大文件
try {
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.FileReader("largefile.txt"));
String line;
int lineCount = 0;
long charCount = 0;
while ((line = reader.readLine()) != null) {
lineCount++;
charCount += line.length();
// 逐行处理,避免加载整个文件到内存
processLine(line);
}
reader.close();
System.out.println("处理完成: " + lineCount + "行, " + charCount + "字符");
} catch (java.io.IOException e) {
e.printStackTrace();
}
// 8. 编码处理最佳实践
System.out.println("\n=== 编码处理 ===");
System.out.println("1. 明确指定字符编码");
System.out.println("2. 统一使用UTF-8");
System.out.println("3. 处理文件时注意BOM头");
// 明确指定编码
try {
String content = "Hello 世界";
byte[] utf8Bytes = content.getBytes("UTF-8");
String decoded = new String(utf8Bytes, "UTF-8");
System.out.println("明确指定UTF-8编码");
} catch (java.io.UnsupportedEncodingException e) {
e.printStackTrace();
}
}
private static String getUserInput() {
return "YES";
}
private static void processLine(String line) {
// 模拟行处理
if (line.length() > 0) {
// 处理逻辑
}
}
// 9. 字符串工具类示例
static class StringUtils {
// 判断字符串是否为空或空白
public static boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}
// 安全地转换为字符串
public static String safeToString(Object obj) {
return obj == null ? "" : obj.toString();
}
// 截断字符串
public static String truncate(String str, int maxLength) {
if (str == null || str.length() <= maxLength) {
return str;
}
return str.substring(0, maxLength) + "...";
}
// 重复字符串
public static String repeat(String str, int times) {
if (times <= 0 || str == null) {
return "";
}
StringBuilder sb = new StringBuilder(str.length() * times);
for (int i = 0; i < times; i++) {
sb.append(str);
}
return sb.toString();
}
}
}
七、总结要点
最重要的知识点:
-
字符串不可变性:String对象一旦创建就不能改变,所有"修改"操作都创建新对象
-
字符串常量池:直接赋值的字符串会进入常量池,相同内容的字符串会复用
-
性能优化:
- 大量拼接用StringBuilder(单线程)或StringBuffer(多线程)
- 避免在循环中使用
+拼接字符串 - 比较字符串用
equals()而不是==
-
编码规范:
- 总是明确指定字符编码(UTF-8)
- 使用
"常量".equals(变量)避免空指针 - 善用字符串工具类减少重复代码