Spring 的 StringUtils
是一个轻量级的字符串工具类,位于 org.springframework.util
包中,提供了丰富的静态方法用于处理字符串操作。它的设计目标是简化常见的字符串处理逻辑,避免重复代码,并针对 Java 字符串的特性进行了优化。本文将从核心功能、源码解析、使用场景及注意事项展开详细说明。
一、核心功能概览
StringUtils
覆盖了字符串处理的几乎所有常见需求,主要包括以下几类:
功能分类 | 核心方法 | 说明 |
---|---|---|
空字符串判断 | isEmpty(CharSequence cs) 、isBlank(CharSequence cs) | 判断字符串是否为 null 、空("" )或空白(仅包含空格、制表符等)。 |
字符串截取 | substring(String str, int start) 、substringBetween(String str, String open, String close) | 截取子字符串,支持指定起始位置或起始/结束分隔符。 |
字符串替换 | replace(CharSequence text, CharSequence target, CharSequence replacement) | 替换字符串中的目标子串为指定内容,支持忽略大小写。 |
大小写转换 | capitalize(String str) 、uncapitalize(String str) 、swapCase(String str) | 首字母大写、小写,或整体大小写反转。 |
字符串分割 | split(String str, String delimiter) 、split(String str, String delimiter, int limit) | 按分隔符分割字符串,支持限制分割次数。 |
字符串修剪 | trim(String str) 、trimAllWhitespace(String str) | 去除首尾空白(trim() )或所有空白(trimAllWhitespace() )。 |
字符串连接 | join(Object[] array, String delimiter) 、join(Collection<?> collection, String delimiter) | 用分隔符连接数组或集合中的元素。 |
其他实用方法 | repeat(String str, int repeat) 、reverse(String str) 、encodeUrlPathSegment(String pathSegment) | 字符串重复、反转、URL 编码等。 |
二、核心方法源码解析
以下选取最常用的方法,结合源码详细说明其实现逻辑和设计细节。
1. 空字符串判断:isEmpty()
和 isBlank()
isEmpty()
判断字符串是否为 null
或空(""
),isBlank()
则进一步判断是否仅包含空白字符(如空格、\t
、\n
等)。
源码实现:
// 判断是否为空(null 或 "")
public static boolean isEmpty(CharSequence cs) {
return cs == null || cs.length() == 0;
}
// 判断是否为空白(null、"" 或仅包含空白字符)
public static boolean isBlank(CharSequence cs) {
int strLen;
if (cs == null || (strLen = cs.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(cs.charAt(i))) { // 检查每个字符是否是空白
return false;
}
}
return true;
}
- 设计细节:
isEmpty()
直接检查null
和长度,时间复杂度O(1)
。isBlank()
遍历字符数组,遇到非空白字符立即返回false
,最坏情况O(n)
(n
为字符串长度)。- 使用
Character.isWhitespace()
判断空白字符,支持 Unicode 空白(如\u00A0
非断行空格)。
2. 字符串截取:substring()
substring(String str, int start)
用于截取从 start
索引开始的子字符串(包含 start
,不包含结束位置)。若 start
超出范围,会自动调整到合理范围。
源码实现:
public static String substring(String str, int start) {
if (str == null) {
return null;
}
// 调整 start 范围:小于 0 则取 0,大于等于长度则取长度(返回空字符串)
int startIdx = Math.max(start, 0);
int endIdx = str.length();
if (startIdx >= endIdx) {
return "";
}
return str.substring(startIdx, endIdx); // 调用 String 的 substring 方法
}
- 设计细节:
- 处理了
start
为负数或超过字符串长度的情况,避免StringIndexOutOfBoundsException
。 - 最终调用
String.substring(int beginIndex, int endIndex)
,底层基于字符数组复制,时间复杂度O(k)
(k
为子字符串长度)。
- 处理了
3. 字符串替换:replace()
replace(CharSequence text, CharSequence target, CharSequence replacement)
用于将 text
中所有 target
子串替换为 replacement
,支持忽略大小写。
源码实现:
public static String replace(CharSequence text, CharSequence target, CharSequence replacement) {
return replace(text, target, replacement, false);
}
// 重载方法:支持忽略大小写(ignoreCase)
private static String replace(CharSequence text, CharSequence target, CharSequence replacement, boolean ignoreCase) {
if (text == null || target == null || replacement == null || target.length() == 0) {
return text.toString(); // target 为空时不替换
}
int textLen = text.length();
int targetLen = target.length();
int replacementLen = replacement.length();
// 构建结果 StringBuilder
StringBuilder sb = new StringBuilder();
int i = 0;
while (i <= textLen - targetLen) {
// 查找 target 的位置(支持忽略大小写)
boolean found = false;
for (int j = 0; j < targetLen; j++) {
char c1 = ignoreCase ? Character.toLowerCase(text.charAt(i + j)) : text.charAt(i + j);
char c2 = ignoreCase ? Character.toLowerCase(target.charAt(j)) : target.charAt(j);
if (c1 != c2) {
break;
}
if (j == targetLen - 1) {
found = true;
}
}
if (found) {
sb.append(replacement);
i += targetLen;
} else {
sb.append(text.charAt(i++));
}
}
// 处理剩余字符
while (i < textLen) {
sb.append(text.charAt(i++));
}
return sb.toString();
}
- 设计细节:
- 手动实现字符串匹配(未使用正则表达式),避免正则的性能开销。
- 支持忽略大小写(通过
Character.toLowerCase()
转换字符比较)。 - 使用
StringBuilder
拼接结果,减少字符串拼接的性能损耗。 - 时间复杂度为
O(n*m)
(n
为文本长度,m
为目标长度),适用于短文本替换;长文本建议使用Pattern
和Matcher
(但StringUtils.replace()
更简单)。
4. 大小写转换:capitalize()
和 swapCase()
capitalize(String str)
将字符串首字母大写,其余小写;swapCase(String str)
反转每个字符的大小写。
源码实现:
// 首字母大写,其余小写
public static String capitalize(String str) {
if (str == null || str.isEmpty()) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}
// 反转每个字符的大小写
public static String swapCase(String str) {
if (str == null) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (Character.isUpperCase(c)) {
sb.append(Character.toLowerCase(c));
} else {
sb.append(Character.toUpperCase(c));
}
}
return sb.toString();
}
- 设计细节:
capitalize()
先截取首字符大写,再截取剩余部分小写,简单直接。swapCase()
遍历每个字符,通过Character.isUpperCase()
判断并转换,时间复杂度O(n)
。
5. 字符串分割:split()
split(String str, String delimiter)
按分隔符分割字符串,支持限制分割次数(可选参数)。
源码实现:
public static String[] split(String str, String delimiter) {
return split(str, delimiter, -1); // 默认分割次数为 -1(不限制)
}
public static String[] split(String str, String delimiter, int limit) {
if (str == null || delimiter == null) {
return null;
}
if (delimiter.length() == 0) {
throw new IllegalArgumentException("Delimiter cannot be empty");
}
int strLen = str.length();
int delimiterLen = delimiter.length();
int count = 0;
int lastIndex = 0;
// 计算分割次数
while (lastIndex <= strLen - delimiterLen) {
int index = str.indexOf(delimiter, lastIndex);
if (index == -1) {
break;
}
count++;
lastIndex = index + delimiterLen;
}
// 分割结果数组的长度
int max = (limit == -1) ? count + 1 : Math.min(count + 1, limit);
String[] result = new String[max];
int i = 0;
lastIndex = 0;
// 执行分割
while (i < max - 1 && lastIndex <= strLen - delimiterLen) {
int index = str.indexOf(delimiter, lastIndex);
if (index == -1) {
break;
}
result[i++] = str.substring(lastIndex, index);
lastIndex = index + delimiterLen;
}
// 处理最后一个子字符串
result[i] = str.substring(lastIndex);
// 若 limit 小于分割次数,截断数组
if (limit != -1 && i < max - 1) {
String[] truncated = new String[i + 1];
System.arraycopy(result, 0, truncated, 0, i + 1);
return truncated;
}
return result;
}
- 设计细节:
- 手动实现分割逻辑(未使用
String.split()
),避免正则表达式的性能开销。 - 支持限制分割次数(
limit
),当limit
为正数时,最多分割limit-1
次。 - 时间复杂度为
O(n*m)
(n
为字符串长度,m
为分隔符长度),适用于短文本分割。
- 手动实现分割逻辑(未使用
三、典型使用场景
StringUtils
在 Spring 框架内部和外部开发中被广泛使用,以下是几个典型场景:
1. 参数校验与空值处理
在 Web 开发中,处理 HTTP 请求参数时,常需判断参数是否为空或空白:
String username = request.getParameter("username");
if (StringUtils.isBlank(username)) {
throw new IllegalArgumentException("用户名不能为空");
}
2. 日志记录与调试
日志中常需截取过长的字符串,避免日志冗余:
String longLog = "这是一条非常长的日志信息,需要截取前 50 个字符...";
String shortLog = StringUtils.substring(longLog, 0, 50); // 截取前 50 字符
logger.debug("日志摘要:{}", shortLog);
3. 数据清洗与转换
处理用户输入时,常需清理空白或转换大小写:
String input = " hello WORLD ";
String cleaned = StringUtils.trimAllWhitespace(input); // "hello WORLD"
String capitalized = StringUtils.capitalize(cleaned); // "Hello world"
4. 字符串拼接与格式化
拼接集合元素时,使用 join
方法简化代码:
List<String> tags = Arrays.asList("spring", "utils", "string");
String tagStr = StringUtils.join(tags, ", "); // "spring, utils, string"
四、注意事项
- 空指针处理:所有方法均对
null
输入做了容错(返回null
或空字符串),但需注意方法链中的潜在NPE
(如str.substring(0)
若str
为null
会抛出异常)。 - 性能优化:
StringUtils
的方法多为手动实现(如分割、替换),避免了正则表达式的开销,适合短文本处理;长文本建议使用StringBuilder
或Pattern
。 - 大小写敏感性:部分方法(如
replace
)支持忽略大小写,但需注意目标字符串的大小写匹配规则(如ignoreCase
参数)。 - 空白字符处理:
isBlank()
和trimAllWhitespace()
支持 Unicode 空白字符(如\u00A0
),但需确认业务场景是否需要严格匹配 ASCII 空格。
五、总结
Spring 的 StringUtils
是一个轻量、实用的字符串工具类,覆盖了常见的字符串操作需求。其核心优势在于:
- 简洁性:方法命名直观,参数设计符合直觉(如
substring(str, start)
直接指定起始位置)。 - 健壮性:对
null
和边界条件做了充分处理,避免运行时异常。 - 性能优化:手动实现核心逻辑(如分割、替换),避免正则表达式的额外开销。
熟练使用 StringUtils
可以显著减少重复代码,提升开发效率。在实际项目中,建议结合具体场景选择合适的方法,并注意处理边界条件和性能问题。