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
调用,但需要手动处理数组长度。
实现步骤:
- 将字符串转换为字符数组:
char[] chars = str.toCharArray();
- 创建新数组,长度比原数组少1:
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);
完整代码示例:
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"(未删除,需调整模式)
但此方法难以直接定位特定索引的字符。更通用的方式是结合 Pattern
和 Matcher
:
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
处理频繁修改的字符串,同时通过参数校验和异常处理确保代码的健壮性。