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 可以显著减少重复代码,提升开发效率。在实际项目中,建议结合具体场景选择合适的方法,并注意处理边界条件和性能问题。
1459

被折叠的 条评论
为什么被折叠?



