Java中如何实现字符串拼接与截取操作?掌握这些基础字符串处理技巧。
- 内容介绍
- 文章标签
- 相关推荐
本文共计3285个文字,预计阅读时间需要14分钟。
Java中对字符串进行拼接和截取是日常开发中最基础也最频繁的操作之一。简单来说,拼接就是将多个字符串合并成一个新的字符串,而截取则是从一个现有字符串中提取出其一部分。
拼接,即将多个字符串合并为一个。例如:
解决方案
在Java里,字符串的拼接和截取都有多种实现方式,各有特点和适用场景。
字符串拼接
最直观的拼接方式莫过于使用加号
+ 运算符。它用起来确实方便,比如:
立即学习“Java免费学习笔记(深入)”;
String part1 = "Hello"; String part2 = "World"; String combined = part1 + " " + part2 + "!"; // 结果: "Hello World!" System.out.println(combined);
这种方式在多数情况下表现良好,尤其是在拼接次数不多的时候。然而,需要注意的是,Java中的
String 对象是不可变的。这意味着每次使用
+ 拼接时,都会创建一个新的
String 对象来存储结果,这在循环中进行大量拼接时可能会导致性能问题,因为会产生很多临时的、很快就会被垃圾回收的对象。
为了解决这个问题,Java提供了
StringBuilder 和
StringBuffer 类。它们是可变的字符序列,更适合进行多次字符串修改操作。
StringBuilder 非线程安全,性能更高;
StringBuffer 线程安全,但性能略低。在单线程环境下,我个人更倾向于使用
StringBuilder。
// 使用 StringBuilder 进行拼接 StringBuilder sb = new StringBuilder(); sb.append("这是").append("一个").append("StringBuilder").append("的例子。"); String result = sb.toString(); System.out.println(result); // 结果: "这是一个StringBuilder的例子。" // 如果在多线程环境,或者需要线程安全,可以使用 StringBuffer StringBuffer sbuf = new StringBuffer(); sbuf.append("线程安全的").append("拼接。"); String safeResult = sbuf.toString(); System.out.println(safeResult); // 结果: "线程安全的拼接。"
String 类自身也提供了一个
concat() 方法用于拼接,但它和
+ 运算符一样,每次调用都会创建新的
String 对象,所以性能上没有本质区别,不建议在循环中大量使用。
String s1 = "Java"; String s2 = "编程"; String s3 = s1.concat(s2); System.out.println(s3); // 结果: "Java编程"
字符串截取
截取字符串主要依赖
String 类的
substring() 方法。它有两个重载版本:
-
substring(int beginIndex): 从指定索引
beginIndex (包含)开始,截取到字符串的末尾。索引是从0开始计数的。
String original = "HelloJavaWorld"; String sub1 = original.substring(5); // 从索引5开始截取 System.out.println(sub1); // 结果: "JavaWorld"
-
substring(int beginIndex, int endIndex): 从指定索引
beginIndex (包含)开始,截取到
endIndex (不包含)为止。
String original = "HelloJavaWorld"; String sub2 = original.substring(0, 5); // 从索引0到索引5(不包含5) System.out.println(sub2); // 结果: "Hello" String sub3 = original.substring(5, 9); // 从索引5到索引9(不包含9) System.out.println(sub3); // 结果: "Java"
理解索引的“包含”和“不包含”非常关键,尤其是在处理边界情况时。
Java字符串拼接的性能差异及选择考量
在Java中,字符串拼接的性能差异是一个老生常谈的话题,但它确实值得深入理解。这背后主要是
String 对象的不可变性在起作用。当我们用
+ 运算符拼接字符串时,比如
String s = "a" + "b" + "c";,在编译阶段,如果所有操作数都是字面量,Java编译器可能会进行优化,直接生成
String s = "abc";。但如果操作数中包含变量,例如
String s = str1 + str2 + str3;,那么在运行时,每次
+ 操作都会创建一个新的
String 对象。想象一下在一个循环里拼接几千几万次,这会产生大量的中间
String 对象,给垃圾回收器带来不小的压力,从而影响程序性能。
concat() 方法也面临同样的问题,它的底层实现和
+ 运算符在变量拼接时是类似的,每次调用都会返回一个新的
String 对象。
而
StringBuilder 和
StringBuffer 的设计初衷就是为了解决这个问题。它们内部维护一个可变的字符数组,
append() 操作不会每次都创建新对象,而是直接在现有数组上进行修改或扩容。只有当你调用
toString() 方法时,才会生成最终的
String 对象。
-
什么时候用
+ 或
concat()?
当你需要拼接的字符串数量非常少,比如两三个,或者是在字符串字面量之间拼接时,用+ 运算符是最简洁方便的。它的可读性很好,对于这种简单场景,性能开销几乎可以忽略不计。
-
什么时候用
StringBuilder?
在大多数需要进行多次字符串拼接的场景,尤其是循环内部,或者需要构建复杂字符串时,StringBuilder 是首选。它性能最好,因为它没有同步开销。
-
什么时候用
StringBuffer?
如果你的应用程序是多线程的,并且多个线程可能同时操作同一个字符串构建器实例,那么StringBuffer 是必要的。它的方法都是同步的,可以保证线程安全,但代价是略微的性能损失。当然,在现代Java开发中,我们更多地会考虑使用
ConcurrentHashMap 这样的并发集合,或者通过其他方式避免共享可变状态,但
StringBuffer 依然是一个有效的选项。
选择哪种方式,归根结底是根据你的具体需求和对性能、线程安全的要求来决定的。没有绝对的“最好”,只有“最合适”。
Java字符串截取时常见的索引陷阱与边界处理
substring() 方法虽然用起来简单,但如果对索引规则理解不透彻,很容易遇到
IndexOutOfBoundsException。这是我在实际开发中遇到过不少次的问题,尤其是在处理用户输入或外部系统传来的不确定长度的字符串时。
substring(beginIndex) 和
substring(beginIndex, endIndex) 都要求传入的索引是合法的。所谓合法,就是它们必须在
[0, length] 这个范围内。
常见的陷阱:
-
beginIndex 小于 0 或大于
length():
String str = "abc"; str.substring(-1); ->
IndexOutOfBoundsException
String str = "abc"; str.substring(4); ->
IndexOutOfBoundsException
beginIndex 必须在
[0, length] 之间。
-
endIndex 小于
beginIndex 或大于
length():
String str = "abc"; str.substring(1, 0); ->
IndexOutOfBoundsException (因为
endIndex 小于
beginIndex)
String str = "abc"; str.substring(0, 4); ->
IndexOutOfBoundsException (因为
endIndex 大于
length())
endIndex 必须在
[beginIndex, length] 之间。记住
endIndex 是不包含的。
边界处理策略:
-
预检查字符串长度: 在调用
substring() 之前,先检查字符串是否为空 (
null),或者长度是否符合预期。这是最基本的防御性编程。
String text = "Hello"; if (text != null && text.length() >= 5) { String part = text.substring(0, 5); System.out.println(part); } else { System.out.println("字符串不符合截取条件"); }
-
利用
indexOf() 和
lastIndexOf() 查找子串位置:
如果你需要根据某个分隔符或特定子串来截取,结合indexOf() 和
lastIndexOf() 是非常实用的。它们返回子串的起始索引,如果找不到则返回
-1,这可以帮助我们避免硬编码索引。
String path = "/usr/local/bin/java"; int lastSlash = path.lastIndexOf("/"); if (lastSlash != -1) { String fileName = path.substring(lastSlash + 1); System.out.println("文件名: " + fileName); // 结果: "java" }
处理空字符串或单字符字符串: 对于这些特殊情况,
substring() 依然有效,但你需要确保你的逻辑能正确处理它们。例如,
"".substring(0) 会得到空字符串,而
"".substring(0,0) 也是空字符串。
使用
String.isEmpty() 或
String.isBlank():
在Java 11及更高版本中,String.isBlank() 可以判断字符串是否为空或只包含空白字符,这比
isEmpty() 更全面,有助于提前过滤掉不适合截取的字符串。
总的来说,处理字符串截取时,时刻牢记索引的有效范围,并结合条件判断来规避潜在的运行时错误,是编写健壮代码的关键。
除了拼接与截取,Java字符串还有哪些常用操作方法?
除了拼接和截取,
String 类提供了一系列丰富的方法,几乎涵盖了日常文本处理的各种需求。这些方法在数据清洗、格式化、校验等场景中扮演着重要角色。
-
获取信息类:
length(): 返回字符串的长度(字符数)。
charAt(int index): 返回指定索引处的字符。
indexOf(String str) /
indexOf(String str, int fromIndex): 返回指定子字符串第一次出现的索引。找不到则返回 -1。
lastIndexOf(String str) /
lastIndexOf(String str, int fromIndex): 返回指定子字符串最后一次出现的索引。
-
比较与查找类:
equals(Object anObject): 比较两个字符串的内容是否相等,区分大小写。
equalsIgnoreCase(String anotherString): 比较两个字符串的内容是否相等,不区分大小写。
contains(CharSequence s): 判断字符串是否包含指定的字符序列。
startsWith(String prefix): 判断字符串是否以指定前缀开头。
endsWith(String suffix): 判断字符串是否以指定后缀结尾。
compareTo(String anotherString): 按字典顺序比较两个字符串。
-
修改与替换类:
replace(char oldChar, char newChar): 将字符串中所有出现的
oldChar 替换为
newChar。
replace(CharSequence target, CharSequence replacement): 将所有出现的
target 字符序列替换为
replacement。
replaceAll(String regex, String replacement): 使用正则表达式替换所有匹配项。
replaceFirst(String regex, String replacement): 使用正则表达式替换第一个匹配项。
trim(): 移除字符串两端的空白字符。
strip(): (Java 11+)更智能地移除两端的空白字符,包括Unicode空白字符。
stripLeading() /
stripTrailing(): (Java 11+)分别移除前导或尾随空白字符。
toLowerCase(): 将字符串转换为小写。
toUpperCase(): 将字符串转换为大写。
-
分割类:
split(String regex): 根据给定的正则表达式将字符串分割成子字符串数组。
split(String regex, int limit): 限制分割的次数。
-
格式化与转换类:
valueOf(各种类型): 将各种数据类型转换为字符串。这是
String.valueOf(123) 这样的静态方法。
toCharArray(): 将字符串转换为字符数组。
getBytes(): 将字符串编码为字节数组。
这些方法共同构成了Java字符串处理的强大工具箱。在实际项目中,我们很少只用到拼接和截取,往往需要组合使用这些方法来完成复杂的文本处理任务,比如解析日志、处理CSV文件、构建SQL查询、验证用户输入格式等等。理解并灵活运用它们,能大大提高开发效率和代码质量。
本文共计3285个文字,预计阅读时间需要14分钟。
Java中对字符串进行拼接和截取是日常开发中最基础也最频繁的操作之一。简单来说,拼接就是将多个字符串合并成一个新的字符串,而截取则是从一个现有字符串中提取出其一部分。
拼接,即将多个字符串合并为一个。例如:
解决方案
在Java里,字符串的拼接和截取都有多种实现方式,各有特点和适用场景。
字符串拼接
最直观的拼接方式莫过于使用加号
+ 运算符。它用起来确实方便,比如:
立即学习“Java免费学习笔记(深入)”;
String part1 = "Hello"; String part2 = "World"; String combined = part1 + " " + part2 + "!"; // 结果: "Hello World!" System.out.println(combined);
这种方式在多数情况下表现良好,尤其是在拼接次数不多的时候。然而,需要注意的是,Java中的
String 对象是不可变的。这意味着每次使用
+ 拼接时,都会创建一个新的
String 对象来存储结果,这在循环中进行大量拼接时可能会导致性能问题,因为会产生很多临时的、很快就会被垃圾回收的对象。
为了解决这个问题,Java提供了
StringBuilder 和
StringBuffer 类。它们是可变的字符序列,更适合进行多次字符串修改操作。
StringBuilder 非线程安全,性能更高;
StringBuffer 线程安全,但性能略低。在单线程环境下,我个人更倾向于使用
StringBuilder。
// 使用 StringBuilder 进行拼接 StringBuilder sb = new StringBuilder(); sb.append("这是").append("一个").append("StringBuilder").append("的例子。"); String result = sb.toString(); System.out.println(result); // 结果: "这是一个StringBuilder的例子。" // 如果在多线程环境,或者需要线程安全,可以使用 StringBuffer StringBuffer sbuf = new StringBuffer(); sbuf.append("线程安全的").append("拼接。"); String safeResult = sbuf.toString(); System.out.println(safeResult); // 结果: "线程安全的拼接。"
String 类自身也提供了一个
concat() 方法用于拼接,但它和
+ 运算符一样,每次调用都会创建新的
String 对象,所以性能上没有本质区别,不建议在循环中大量使用。
String s1 = "Java"; String s2 = "编程"; String s3 = s1.concat(s2); System.out.println(s3); // 结果: "Java编程"
字符串截取
截取字符串主要依赖
String 类的
substring() 方法。它有两个重载版本:
-
substring(int beginIndex): 从指定索引
beginIndex (包含)开始,截取到字符串的末尾。索引是从0开始计数的。
String original = "HelloJavaWorld"; String sub1 = original.substring(5); // 从索引5开始截取 System.out.println(sub1); // 结果: "JavaWorld"
-
substring(int beginIndex, int endIndex): 从指定索引
beginIndex (包含)开始,截取到
endIndex (不包含)为止。
String original = "HelloJavaWorld"; String sub2 = original.substring(0, 5); // 从索引0到索引5(不包含5) System.out.println(sub2); // 结果: "Hello" String sub3 = original.substring(5, 9); // 从索引5到索引9(不包含9) System.out.println(sub3); // 结果: "Java"
理解索引的“包含”和“不包含”非常关键,尤其是在处理边界情况时。
Java字符串拼接的性能差异及选择考量
在Java中,字符串拼接的性能差异是一个老生常谈的话题,但它确实值得深入理解。这背后主要是
String 对象的不可变性在起作用。当我们用
+ 运算符拼接字符串时,比如
String s = "a" + "b" + "c";,在编译阶段,如果所有操作数都是字面量,Java编译器可能会进行优化,直接生成
String s = "abc";。但如果操作数中包含变量,例如
String s = str1 + str2 + str3;,那么在运行时,每次
+ 操作都会创建一个新的
String 对象。想象一下在一个循环里拼接几千几万次,这会产生大量的中间
String 对象,给垃圾回收器带来不小的压力,从而影响程序性能。
concat() 方法也面临同样的问题,它的底层实现和
+ 运算符在变量拼接时是类似的,每次调用都会返回一个新的
String 对象。
而
StringBuilder 和
StringBuffer 的设计初衷就是为了解决这个问题。它们内部维护一个可变的字符数组,
append() 操作不会每次都创建新对象,而是直接在现有数组上进行修改或扩容。只有当你调用
toString() 方法时,才会生成最终的
String 对象。
-
什么时候用
+ 或
concat()?
当你需要拼接的字符串数量非常少,比如两三个,或者是在字符串字面量之间拼接时,用+ 运算符是最简洁方便的。它的可读性很好,对于这种简单场景,性能开销几乎可以忽略不计。
-
什么时候用
StringBuilder?
在大多数需要进行多次字符串拼接的场景,尤其是循环内部,或者需要构建复杂字符串时,StringBuilder 是首选。它性能最好,因为它没有同步开销。
-
什么时候用
StringBuffer?
如果你的应用程序是多线程的,并且多个线程可能同时操作同一个字符串构建器实例,那么StringBuffer 是必要的。它的方法都是同步的,可以保证线程安全,但代价是略微的性能损失。当然,在现代Java开发中,我们更多地会考虑使用
ConcurrentHashMap 这样的并发集合,或者通过其他方式避免共享可变状态,但
StringBuffer 依然是一个有效的选项。
选择哪种方式,归根结底是根据你的具体需求和对性能、线程安全的要求来决定的。没有绝对的“最好”,只有“最合适”。
Java字符串截取时常见的索引陷阱与边界处理
substring() 方法虽然用起来简单,但如果对索引规则理解不透彻,很容易遇到
IndexOutOfBoundsException。这是我在实际开发中遇到过不少次的问题,尤其是在处理用户输入或外部系统传来的不确定长度的字符串时。
substring(beginIndex) 和
substring(beginIndex, endIndex) 都要求传入的索引是合法的。所谓合法,就是它们必须在
[0, length] 这个范围内。
常见的陷阱:
-
beginIndex 小于 0 或大于
length():
String str = "abc"; str.substring(-1); ->
IndexOutOfBoundsException
String str = "abc"; str.substring(4); ->
IndexOutOfBoundsException
beginIndex 必须在
[0, length] 之间。
-
endIndex 小于
beginIndex 或大于
length():
String str = "abc"; str.substring(1, 0); ->
IndexOutOfBoundsException (因为
endIndex 小于
beginIndex)
String str = "abc"; str.substring(0, 4); ->
IndexOutOfBoundsException (因为
endIndex 大于
length())
endIndex 必须在
[beginIndex, length] 之间。记住
endIndex 是不包含的。
边界处理策略:
-
预检查字符串长度: 在调用
substring() 之前,先检查字符串是否为空 (
null),或者长度是否符合预期。这是最基本的防御性编程。
String text = "Hello"; if (text != null && text.length() >= 5) { String part = text.substring(0, 5); System.out.println(part); } else { System.out.println("字符串不符合截取条件"); }
-
利用
indexOf() 和
lastIndexOf() 查找子串位置:
如果你需要根据某个分隔符或特定子串来截取,结合indexOf() 和
lastIndexOf() 是非常实用的。它们返回子串的起始索引,如果找不到则返回
-1,这可以帮助我们避免硬编码索引。
String path = "/usr/local/bin/java"; int lastSlash = path.lastIndexOf("/"); if (lastSlash != -1) { String fileName = path.substring(lastSlash + 1); System.out.println("文件名: " + fileName); // 结果: "java" }
处理空字符串或单字符字符串: 对于这些特殊情况,
substring() 依然有效,但你需要确保你的逻辑能正确处理它们。例如,
"".substring(0) 会得到空字符串,而
"".substring(0,0) 也是空字符串。
使用
String.isEmpty() 或
String.isBlank():
在Java 11及更高版本中,String.isBlank() 可以判断字符串是否为空或只包含空白字符,这比
isEmpty() 更全面,有助于提前过滤掉不适合截取的字符串。
总的来说,处理字符串截取时,时刻牢记索引的有效范围,并结合条件判断来规避潜在的运行时错误,是编写健壮代码的关键。
除了拼接与截取,Java字符串还有哪些常用操作方法?
除了拼接和截取,
String 类提供了一系列丰富的方法,几乎涵盖了日常文本处理的各种需求。这些方法在数据清洗、格式化、校验等场景中扮演着重要角色。
-
获取信息类:
length(): 返回字符串的长度(字符数)。
charAt(int index): 返回指定索引处的字符。
indexOf(String str) /
indexOf(String str, int fromIndex): 返回指定子字符串第一次出现的索引。找不到则返回 -1。
lastIndexOf(String str) /
lastIndexOf(String str, int fromIndex): 返回指定子字符串最后一次出现的索引。
-
比较与查找类:
equals(Object anObject): 比较两个字符串的内容是否相等,区分大小写。
equalsIgnoreCase(String anotherString): 比较两个字符串的内容是否相等,不区分大小写。
contains(CharSequence s): 判断字符串是否包含指定的字符序列。
startsWith(String prefix): 判断字符串是否以指定前缀开头。
endsWith(String suffix): 判断字符串是否以指定后缀结尾。
compareTo(String anotherString): 按字典顺序比较两个字符串。
-
修改与替换类:
replace(char oldChar, char newChar): 将字符串中所有出现的
oldChar 替换为
newChar。
replace(CharSequence target, CharSequence replacement): 将所有出现的
target 字符序列替换为
replacement。
replaceAll(String regex, String replacement): 使用正则表达式替换所有匹配项。
replaceFirst(String regex, String replacement): 使用正则表达式替换第一个匹配项。
trim(): 移除字符串两端的空白字符。
strip(): (Java 11+)更智能地移除两端的空白字符,包括Unicode空白字符。
stripLeading() /
stripTrailing(): (Java 11+)分别移除前导或尾随空白字符。
toLowerCase(): 将字符串转换为小写。
toUpperCase(): 将字符串转换为大写。
-
分割类:
split(String regex): 根据给定的正则表达式将字符串分割成子字符串数组。
split(String regex, int limit): 限制分割的次数。
-
格式化与转换类:
valueOf(各种类型): 将各种数据类型转换为字符串。这是
String.valueOf(123) 这样的静态方法。
toCharArray(): 将字符串转换为字符数组。
getBytes(): 将字符串编码为字节数组。
这些方法共同构成了Java字符串处理的强大工具箱。在实际项目中,我们很少只用到拼接和截取,往往需要组合使用这些方法来完成复杂的文本处理任务,比如解析日志、处理CSV文件、构建SQL查询、验证用户输入格式等等。理解并灵活运用它们,能大大提高开发效率和代码质量。

