Java字符串操作完全指南:从基础到高级实战

59 阅读10分钟

在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();
        }
    }
}

七、总结要点

最重要的知识点:

  1. 字符串不可变性:String对象一旦创建就不能改变,所有"修改"操作都创建新对象

  2. 字符串常量池:直接赋值的字符串会进入常量池,相同内容的字符串会复用

  3. 性能优化

    • 大量拼接用StringBuilder(单线程)或StringBuffer(多线程)
    • 避免在循环中使用+拼接字符串
    • 比较字符串用equals()而不是==
  4. 编码规范

    • 总是明确指定字符编码(UTF-8)
    • 使用"常量".equals(变量)避免空指针
    • 善用字符串工具类减少重复代码