Spring 常用类深度剖析(工具篇 03):StringUtils——那些你意想不到的实用方法
文章目录
引言
在前两篇文章中,我们分别探讨了
BeanUtils和ReflectionUtils两个强大的工具类。今天要聊的StringUtils可能是大家最“熟悉”的一个——毕竟每个项目都离不开字符串处理。但你可能不知道,Spring 自带的StringUtils藏着许多冷门却极为实用的方法,能在日常开发中帮你省下大量重复代码。
Spring 常用类深度解析系列开篇:那些我们每天都在用,却未必真正理解的类
提起字符串工具类,很多人第一时间会想到 Apache Commons Lang 的 StringUtils,或者 Guava 的 Strings。Spring 的 StringUtils 虽然名气稍逊,但它与 Spring 生态紧密结合,提供了许多专为框架场景设计的功能(如资源路径处理、区域解析、属性占位符等)。本文将深入 Spring StringUtils 的底层,挖掘那些“意想不到”的实用方法,并对比它们与流行替代方案的区别。
一、为什么要用 Spring 的 StringUtils?
因为 Spring 的 StringUtils 针对配置文件处理、国际化、Web 路径、集合转换等场景做了深度优化,这些功能在 Apache Commons 中要么没有,要么用法不同。掌握这些方法,可以让你的代码更符合 Spring 生态的习惯,也更简洁。
1.1 常见字符串操作的痛点
在实际开发中,我们经常需要:
- 判断字符串是否为空或空白
- 将逗号分隔的字符串转为集合
- 解析 Locale(如
zh_CN或en_US) - 将字符串转换为数组(考虑转义和引号)
- 处理文件路径或 URL 的拼接
原生 JDK 提供的 String 方法往往不够用,我们不得不自己写工具类或引入第三方库。Spring 的 StringUtils 正好填补了这些空白,且没有额外依赖,是 Spring Boot 项目中的天然选择。
1.2 与 Apache Commons Lang 的差异
| 特性 | Spring StringUtils | Apache Commons Lang StringUtils |
|---|---|---|
| 包名 | org.springframework.util |
org.apache.commons.lang3 |
| 定位 | 轻量级,聚焦 Spring 框架需求 | 功能全面,通用工具库 |
| 依赖 | 无额外依赖 | 需引入 commons-lang3 |
| 特色方法 | 路径处理、区域解析、集合转换、Spring 专属格式 | null-safe 操作、判空、填充、替换、分割等字符串算法(Levenshtein距离等) |
| 性能 | 针对常见场景优化 | 功能丰富,性能稳定 |
Spring StringUtils 不追求大而全,但在某些领域提供了更符合 Java/Spring 习惯的 API。
如果你的项目已经用了 Spring,对于常见的基础操作(判空、分割、拼接)两者都可以;对于Spring 特有的场景(如路径匹配、资源解析),Spring 版本是首选。
二、核心 API 深度解析与实战
2.1 判空与空字符串处理
isEmpty vs hasLength vs hasText
Spring 提供了三个层次的判空方法:
public void testStringUtils() {
String str = "";
// 1. 判断对象是否为 null,或字符串长度为 0
// @deprecated as of 5.3, in favor of {@link #hasLength(String)}
// and {@link #hasText(String)} (or {@link ObjectUtils#isEmpty(Object)})
boolean empty = StringUtils.isEmpty(str); // null / "" → true
// 2. 判断字符串长度是否 > 0(不为 null 且长度 > 0)
boolean hasLength = StringUtils.hasLength(str); // null / "" → false
// 3. 判断是否包含非空白字符(不为 null,长度 > 0 且不全是空白)
boolean hasText = StringUtils.hasText(str); // " " → false
}
实战场景:在 Controller 层校验请求参数时,hasText 通常最合适,因为它会过滤掉空字符串和空白字符。
@PostMapping("/user")
public ResponseEntity<?> createUser(@RequestBody UserDto dto) {
if (!StringUtils.hasText(dto.getName())) {
return ResponseEntity.badRequest().body("姓名不能为空");
}
// ...
}
trimAllWhitespace 与 trimWhitespace
trimWhitespace:去掉首尾空白,保留内部空白。trimAllWhitespace:去掉所有空白字符(包括空格、制表符、换行等)。
String s = " Hello World ";
System.out.println(StringUtils.trimWhitespace(s)); // "Hello World"
System.out.println(StringUtils.trimAllWhitespace(s)); // "HelloWorld"
2.2 集合转换:字符串 ↔ 集合
这是 Spring StringUtils 的一大亮点,能将带分隔符的字符串轻松转换为 Set、List 或 String[]。
commaDelimitedListToSet
将逗号分隔的字符串转换为 LinkedHashSet(去重且保持顺序):
String tags = "java,spring,spring,redis";
Set<String> tagSet = StringUtils.commaDelimitedListToSet(tags);
// [java, spring, redis]
commaDelimitedListToStringArray
与上述类似,返回 String[] 数组,保留重复元素:
String[] array = StringUtils.commaDelimitedListToStringArray("a,b,c,c");
// ["a", "b", "c", "c"]
tokenizeToStringArray:更强大的分词器
支持自定义分隔符、忽略空白、忽略空标记(如连续分隔符导致的空字符串):
String input = "hello;world;; spring ";
// 分隔符为 ; 和空格,忽略空白标记,跳过空标记
String[] tokens = StringUtils.tokenizeToStringArray(
input, "; ", true, true);
// ["hello", "world", "spring"]
参数说明:
trimTokens:是否去除每个 token 的前后空白ignoreEmptyTokens:是否忽略空 token
这个方法的灵活性远超简单的 split,因为它能处理多种分隔符、自动修剪空白,并智能处理连续分隔符。
2.3 路径与 URL 处理
Spring 经常处理资源路径,因此 StringUtils 内置了路径规范化方法。
applyRelativePath:根据基路径计算相对路径
String base = "/WEB-INF/classes/";
String relative = "com/example/App.class";
String full = StringUtils.applyRelativePath(base, relative);
// "/WEB-INF/classes/com/example/App.class"
注意:它只是字符串拼接,不会做文件系统校验。
cleanPath:清理路径中的冗余元素
将路径中的 ./、../ 等规范化,适用于 URL 或文件路径。
String messy = "/a/./b/../c/d/";
String cleaned = StringUtils.cleanPath(messy); // "/a/c/d"
2.4 区域解析:parseLocaleString
将类似 zh_CN、en_US 的字符串解析为 Locale 对象:
Locale locale = StringUtils.parseLocaleString("zh_CN");
// Locale("zh", "CN")
Locale locale2 = StringUtils.parseLocaleString("en");
// Locale("en")
// 支持变体:zh_CN_#Hans
Locale locale3 = StringUtils.parseLocaleString("zh_CN_#Hans");
// Locale("zh", "CN", "Hans")
这个功能在国际化配置中非常实用,比如从配置文件读取语言标签。
2.5 数组与字符串互转
arrayToDelimitedString:将数组拼接成字符串
String[] arr = {"a", "b", "c"};
String result = StringUtils.arrayToDelimitedString(arr, "|");
// "a|b|c"
collectionToDelimitedString:同样适用于集合
List<String> list = Arrays.asList("x", "y", "z");
String result = StringUtils.collectionToDelimitedString(list, ",", "'", "'");
// "'x','y','z'"
第三个和第四个参数分别表示前缀和后缀,常用于生成 SQL 的 IN 子句。
2.6 占位符替换:replace 系列
Spring 的 StringUtils 提供了多种替换方法:
replace(String inString, String oldPattern, String newPattern):全局替换delete(String inString, String pattern):删除所有出现的子串deleteAny(String inString, String charsToDelete):删除任意指定字符
String s = "spring spring spring";
String replaced = StringUtils.replace(s, "spring", "boot");
// "boot boot boot"
String deleted = StringUtils.delete(s, "ing");
// "spr spr spr"
2.7 文件扩展名提取
getFilenameExtension 和 stripFilenameExtension 是专门处理文件名的工具:
String file = "myfile.tar.gz";
String ext = StringUtils.getFilenameExtension(file); // "gz"
String name = StringUtils.stripFilenameExtension(file); // "myfile.tar"
2.8 其他冷门但实用的方法
| 方法 | 功能 | 示例 |
|---|---|---|
countOccurrencesOf |
统计子串出现次数 | countOccurrencesOf("ababa", "aba") → 1(非重叠) |
quote / unquote |
添加/移除引号 | quote("hello") → "'hello'" |
capitalize / uncapitalize |
首字母大小写转换 | capitalize("spring") → "Spring" |
getFilename |
从路径中提取文件名 | getFilename("/a/b/c.txt") → "c.txt" |
三、源码探秘:设计思路与关键实现
3.1 判空方法的实现
isEmpty 的实现非常直接:
hasLength 则判断长度:
而 hasText 在 hasLength 基础上增加了空白字符检查:
其中 containsText 会循环检查每个字符是否不是空白(!Character.isWhitespace(ch))。
3.2 tokenizeToStringArray 的解析逻辑
该方法核心是一个基于索引的状态机,支持多分隔符。关键步骤:
- 遍历输入字符串,定位 token 的起始和结束位置。
- 根据
trimTokens决定是否修剪 token 两端空白。 - 根据
ignoreEmptyTokens决定是否丢弃空 token。 - 使用
String的charAt逐个比较分隔符,效率较高。

性能考量
tokenizeToStringArray内部使用String.indexOf和String.substring,时间复杂度 O(n),适合中小规模字符串。
所有方法均没有缓存,每次调用都会重新计算,但对于非极端高频场景足够。
如果需要进行海量字符串操作,建议使用更底层的StringBuilder或第三方库如fastutils。
3.3 parseLocaleString 的解析细节
该方法严格按照 Java 的 Locale 规范解析:
- 下划线
_分割语言、国家、变体 - 可选
#后缀表示变体扩展 - 使用
Locale.Builder构造(JDK 7+)

四、实战案例:一个简单的配置解析器
结合以上方法,我们可以快速实现一个配置解析器,将类似 key=value 的文本转为 Properties,并支持逗号分隔的多值配置。
public class ConfigParser {
public static Properties parse(String configText) {
Properties props = new Properties();
String[] lines = StringUtils.tokenizeToStringArray(configText, "\n", true, true);
for (String line : lines) {
if (!StringUtils.hasText(line) || line.trim().startsWith("#")) {
continue; // 跳过空行和注释
}
int eqIndex = line.indexOf('=');
if (eqIndex == -1) continue;
String key = line.substring(0, eqIndex).trim();
String value = line.substring(eqIndex + 1).trim();
// 如果 value 是逗号分隔,转换为 Set
if (value.contains(",")) {
Set<String> set = StringUtils.commaDelimitedListToSet(value);
props.put(key, set);
} else {
props.put(key, value);
}
}
return props;
}
public static void main(String[] args) {
String cfg = "name=Spring\n" +
"tags=java,spring,boot\n" +
"# 注释行\n" +
" version = 5.3 ";
Properties props = parse(cfg);
System.out.println(props.get("name")); // Spring
System.out.println(props.get("tags")); // [java, spring, boot]
System.out.println(props.get("version")); // 5.3
}
}
这个例子展示了如何利用 tokenizeToStringArray 分割行,commaDelimitedListToSet 处理多值,以及 hasText 过滤空白行。
五、与 Apache Commons Lang StringUtils 的对比总结
| 场景 | Spring StringUtils | Apache Commons Lang StringUtils |
|---|---|---|
| 判空/空白 | hasText |
isNotBlank |
| 集合转换 | 内置 commaDelimitedListToSet 等 |
需自行实现或借助其他工具 |
| 路径处理 | cleanPath, applyRelativePath |
无 |
| 区域解析 | parseLocaleString |
无 |
| 字符串算法 | 无 | 丰富(Levenshtein、JaroWinkler 等) |
| 随机字符串 | 无 | randomAlphanumeric |
| 性能 | 轻量级,适合 Spring 应用 | 功能全面,但额外引入依赖 |
六、源码亮点:Spring 的设计哲学
阅读 StringUtils 源码,你会发现几个值得学习的点:
- 极致的 null-safety
几乎每个方法都会先检查输入是否为null,返回合理的默认值(通常是null或空数组)。例如commaDelimitedListToSet遇到null返回空Set,而不是抛出 NPE。 - 使用 LinkedHashSet 保留顺序
在commaDelimitedListToSet中,返回的是LinkedHashSet,既去重又保留原始顺序,这在配置解析场景非常实用。 - 高效的数组扩容策略
在delimitedListToStringArray内部,使用了类似ArrayList的动态数组扩容算法(初始容量 10,每次按需扩容),避免频繁创建新数组。 - 不依赖 JDK 高版本特性
Spring 为了保持兼容性,StringUtils中的方法都是基于 JDK 8 的 API 实现的,没有使用String#isBlank(JDK 11 引入)等新特性。这也给了我们一个启示:工具类要保守,保持最低依赖。
七、总结
Spring 的 StringUtils 绝不是鸡肋,它专注于解决 Spring 生态中常见的字符串处理需求,尤其在路径、区域、配置解析等方面表现出色。虽然它在通用性上不如 Apache Commons,但在 Spring 项目中,它是最顺手、最轻量的选择。本文重点介绍了:
- 判空方法的层次(
isEmpty→hasLength→hasText) - 强大的分词与集合转换方法(
tokenizeToStringArray、commaDelimitedListToSet) - 路径处理与区域解析等 Spring 特色功能
- 一些容易被忽略但实用的小工具(
countOccurrencesOf、quote、getFilenameExtension)
建议在日常开发中记住以下几点:
- 遇到逗号分隔的配置,优先想到
commaDelimitedListToSet。 - 处理文件路径时,用
cleanPath规范路径。 - 国际化场景,用
parseLocaleString和toLanguageTag。 - 不要重复造轮子,Spring 已经帮你写好了。
下一篇文章,我们将继续 “Spring 常用类深度剖析(工具篇 04):CollectionUtils 与 Stream API 的对比与融合”,看看 Spring 在集合操作上又提供了哪些惊喜。敬请期待!
思考题:StringUtils.trimAllWhitespace 和 String.replaceAll("\\s+", "") 有何异同?在大量字符串处理时,哪个性能更好?(欢迎在评论区留下你的答案)
更多推荐



所有评论(0)