Java 实例 – 删除字符串中的一个字符(千字长文)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

在 Java 编程中,字符串操作是一项基础且高频的任务。删除字符串中的一个字符看似简单,却涉及字符串的不可变性、内存管理、性能优化等核心概念。无论是处理用户输入、格式化输出,还是数据清洗,这一技能都能帮助开发者高效解决问题。本文将通过循序渐进的方式,结合具体案例和代码示例,深入探讨 Java 中删除指定位置字符的多种方法,并分析它们的适用场景与优劣。


字符串的基本概念与特性

不可变性:字符串的“珠子项链”比喻

在 Java 中,字符串(String)是一个不可变对象。这意味着一旦创建了一个字符串对象,其内容就无法被修改。想象字符串像一条串好的珠子项链,每颗珠子代表一个字符:如果想要调整项链的长度或替换某颗珠子,只能重新制作一条新项链,而不是直接修改原项链。例如:

String original = "hello";  
original = original.substring(0, 1) + original.substring(2); // 新对象被创建  

上述代码看似修改了字符串,实则通过拼接生成了新字符串,原字符串仍保留在内存中。这一特性要求我们在操作字符串时,必须通过生成新对象的方式实现修改。

索引与边界问题

Java 字符串的字符索引从 0 开始,最后一个字符的索引是 length() - 1。删除字符时,需要明确目标字符的位置。例如,字符串 "apple" 的索引分布如下:
| 索引 | 0 | 1 | 2 | 3 | 4 |
|------|---|---|---|---|---|
| 字符 | a | p | p | l | e |

若尝试删除索引为 5 的字符(超出范围),程序会抛出 StringIndexOutOfBoundsException。因此,在操作前必须检查索引是否合法。


方法一:使用字符串拼接(Substring 拼接法)

原理:切割与重组

通过 substring(int beginIndex, int endIndex) 方法,可以将原字符串切割为两部分,再拼接成新字符串,从而“跳过”指定位置的字符。例如,删除索引 i 处的字符:

public static String removeCharBySubstring(String str, int index) {  
    if (index < 0 || index >= str.length()) {  
        throw new IllegalArgumentException("Invalid index");  
    }  
    return str.substring(0, index) + str.substring(index + 1);  
}  

案例演示

String s = "example";  
System.out.println(removeCharBySubstring(s, 3)); // 输出 "exple"(删除索引3的字符'm')  

适用场景与局限性

此方法简单直观,适合短字符串或单次操作。但频繁使用时,由于 substring 会产生多个临时对象,可能导致内存浪费。例如,删除字符串 "test" 的第二个字符时,需生成 sub1 = "te"sub2 = "t",最终合并为 "tt"


方法二:使用字符数组操作

转换为可变结构:数组的“橡皮擦”特性

将字符串转换为字符数组(char[]),通过遍历并跳过目标索引,最后将数组转回字符串。这种方法避免了多次 substring 调用,但需要手动处理数组长度。

实现步骤:

  1. 将字符串转换为字符数组:char[] chars = str.toCharArray();
  2. 创建新数组,长度比原数组少1:char[] newChars = new char[chars.length - 1];
  3. 遍历原数组,跳过目标索引:
    for (int i = 0, j = 0; i < chars.length; i++) {  
        if (i != index) {  
            newChars[j++] = chars[i];  
        }  
    }  
    
  4. 将新数组转为字符串:return new String(newChars);

完整代码示例

public static String removeCharByCharArray(String str, int index) {  
    if (index < 0 || index >= str.length()) {  
        return str; // 或抛出异常  
    }  
    char[] chars = str.toCharArray();  
    char[] newChars = new char[chars.length - 1];  
    for (int i = 0, j = 0; i < chars.length; i++) {  
        if (i != index) {  
            newChars[j++] = chars[i];  
        }  
    }  
    return new String(newChars);  
}  

性能对比

此方法的时间复杂度为 O(n),但减少了中间对象的创建,适合处理中等长度的字符串。例如,删除长度为1000的字符串时,字符数组方法比多次 substring 拼接更高效。


方法三:通过 StringBuilder 类实现

可变字符串的“橡皮筋”特性

StringBuilder 是 Java 提供的可变字符串类,通过 deleteCharAt(int index) 方法可直接删除指定位置字符,无需手动拼接。

代码示例:

public static String removeCharByStringBuilder(String str, int index) {  
    if (index < 0 || index >= str.length()) {  
        return str;  
    }  
    StringBuilder sb = new StringBuilder(str);  
    sb.deleteCharAt(index);  
    return sb.toString();  
}  

性能优势

  • 内存效率高:仅修改内部字符数组,避免创建多个临时对象。
  • 适合频繁操作:例如在循环中多次修改字符串时,StringBuilder 的性能显著优于拼接法。

注意事项

StringBuilder 是线程不安全的,若需多线程环境,应改用 StringBuffer。但大多数单线程场景下,StringBuilder 更高效。


方法四:正则表达式匹配与替换

模式匹配的“万能钥匙”

利用正则表达式(Regex)的 replaceAll 方法,通过匹配特定位置的字符进行替换。例如,删除第一个 a

String str = "apple";  
String result = str.replaceAll("^.*?(a).*", "$1"); // 输出 "apple"(未删除,需调整模式)  

但此方法难以直接定位特定索引的字符。更通用的方式是结合 PatternMatcher

public static String removeCharByRegex(String str, int index) {  
    if (index < 0 || index >= str.length()) {  
        return str;  
    }  
    String regex = "(?s).{" + index + "}(.);" // 需要更精确的模式设计  
    // 实际应用中建议使用其他方法,正则在此场景效率较低  
}  

局限性:正则表达式在此场景中不够直观,且性能可能低于其他方法,故不推荐作为首选方案。


性能对比与选择建议

各方法的时空复杂度分析

方法时间复杂度空间复杂度适用场景
字符串拼接法O(n)O(n)短字符串、简单操作
字符数组法O(n)O(n)需避免临时对象的中等长度字符串
StringBuilder 法O(1)*O(n)频繁修改的长字符串
正则表达式法O(n)O(n)特殊模式匹配(不推荐此场景)

*注:StringBuilder.deleteCharAt 的时间复杂度为 O(1),但内部数组可能扩容导致额外开销。

选择建议:

  • 短字符串或单次操作字符串拼接法(代码简洁)。
  • 中等长度字符串或需避免临时对象字符数组法
  • 频繁修改或长字符串StringBuilder

进阶技巧:动态参数与异常处理

参数校验与容错设计

在实际开发中,需确保索引合法。例如:

public static String safeRemoveChar(String str, int index) {  
    if (str == null || str.isEmpty()) {  
        return str;  
    }  
    if (index < 0 || index >= str.length()) {  
        throw new IllegalArgumentException("Index out of bounds");  
    }  
    // 执行删除逻辑  
}  

删除多个字符的扩展

若需删除多个指定位置的字符,可结合 StringBuilder 和集合存储索引:

public static String removeMultipleChars(String str, List<Integer> indexes) {  
    StringBuilder sb = new StringBuilder(str);  
    for (int i = indexes.size() - 1; i >= 0; i--) { // 逆序删除避免索引偏移  
        int index = indexes.get(i);  
        if (index < sb.length()) {  
            sb.deleteCharAt(index);  
        }  
    }  
    return sb.toString();  
}  

实际应用场景与扩展思考

场景一:用户输入清洗

例如,移除密码中的特殊字符:

String userInput = "p@ssword!";  
String cleaned = removeCharByStringBuilder(userInput, 2); // 删除索引2的 '@'  

场景二:日志格式化

删除日志中的敏感字符:

String logEntry = "User=John, Age=30, SSN=123-45-6789";  
String sanitized = removeCharBySubstring(logEntry, logEntry.indexOf('-')); // 删除第一个 '-'  

扩展思考:面向对象封装

将删除逻辑封装为工具类,提供多种静态方法:

public class StringManipulator {  
    public static String removeAt(String str, int index) {  
        // 调用最优方法实现  
    }  
    // 其他工具方法  
}  

结论

Java 中删除字符串中的一个字符,看似简单实则蕴含多个关键概念:字符串不可变性、性能优化、API 使用等。通过本文的四种方法对比与案例分析,开发者可根据具体场景选择最合适的方案。无论是初学者理解基础操作,还是中级开发者优化复杂逻辑,掌握这些技巧都能显著提升编码效率与代码质量。在实际项目中,建议优先使用 StringBuilder 处理频繁修改的字符串,同时通过参数校验和异常处理确保代码的健壮性。

最新发布