package com.tudicloud.framework.common.core.util.core;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 字符串工具类
*
* @Author 孙达
* @Date 2025/11/14 14:11
* @Wechat sundaain
* @Email 18211102099@163.com
* @Copyright <a href="https://www.sundablog.com">孙达博客</a>
*/
public class StringUtils {
/**
* 空字符串常量:{@code ""}。
*/
public static final String EMPTY = "";
/**
* 表示未找到索引时的返回值。
*/
public static final int INDEX_NOT_FOUND = -1;
// ---------------------- empty ----------------------
/**
* 判断字符串是否为空(null 或长度为0)。
*
* <pre>
* StringUtils.isEmpty(null) = true
* StringUtils.isEmpty("") = true
* StringUtils.isEmpty(" ") = false
* StringUtils.isEmpty("bob") = false
* StringUtils.isEmpty(" bob ") = false
* </pre>
*
* @param str 待判断的字符串
* @return 如果字符串为 null 或空则返回 true,否则返回 false
*/
public static boolean isEmpty(String str) {
return str == null || str.isEmpty();
}
/**
* 判断字符串是否不为空。
*
* <pre>
* StringUtils.isNotEmpty(null) = false
* StringUtils.isNotEmpty("") = false
* StringUtils.isNotEmpty(" ") = true
* StringUtils.isNotEmpty("bob") = true
* StringUtils.isNotEmpty(" bob ") = true
* </pre>
*
* @param str 待判断的字符串
* @return 如果字符串不为 null 且不为空则返回 true,否则返回 false
*/
public static boolean isNotEmpty(String str) {
return !StringUtils.isEmpty(str);
}
// ---------------------- blank ----------------------
/**
* 判断字符串是否为空白(null、空或仅由空白字符组成)。
*
* <pre>
* StringUtils.isBlank(null) = true
* StringUtils.isBlank("") = true
* StringUtils.isBlank(" ") = true
* StringUtils.isBlank("bob") = false
* StringUtils.isBlank(" bob ") = false
* </pre>
*
* @param str 待判断的字符串
* @return 如果字符串为 null、空或全为空白字符则返回 true,否则返回 false
*/
public static boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}
/**
* 判断字符串是否不为空白。
*
* <pre>
* StringUtils.isNotBlank(null) = false
* StringUtils.isNotBlank("") = false
* StringUtils.isNotBlank(" ") = false
* StringUtils.isNotBlank("bob") = true
* StringUtils.isNotBlank(" bob ") = true
* </pre>
*
* @param str 待判断的字符串
* @return 如果字符串不是 null、空或全为空白字符则返回 true,否则返回 false
*/
public static boolean isNotBlank(String str) {
return !StringUtils.isBlank(str);
}
// ---------------------- trim ----------------------
/**
* 去除字符串首尾空白字符。
*
* <pre>
* StringUtils.trim(null) = null
* StringUtils.trim("") = ""
* StringUtils.trim(" ") = ""
* StringUtils.trim("abc") = "abc"
* StringUtils.trim(" abc ") = "abc"
* </pre>
*
* @param str 要处理的字符串
* @return 处理后的字符串,如果原字符串为 null 则返回 null
*/
public static String trim(String str) {
return str == null ? null : str.trim();
}
/**
* 去除字符串首尾空白字符,并在结果为空时返回 null。
*
* <pre>
* StringUtils.trimToNull(null) = null
* StringUtils.trimToNull("") = null
* StringUtils.trimToNull(" ") = null
* StringUtils.trimToNull("abc") = "abc"
* StringUtils.trimToNull(" abc ") = "abc"
* </pre>
*
* @param str 要处理的字符串
* @return 处理后的内容;若结果为空则返回 null
*/
public static String trimToNull(String str) {
String ts = trim(str);
return isEmpty(ts) ? null : ts;
}
/**
* 去除字符串首尾空白字符,并在输入为 null 时返回空字符串。
*
* <pre>
* StringUtils.trimToEmpty(null) = ""
* StringUtils.trimToEmpty("") = ""
* StringUtils.trimToEmpty(" ") = ""
* StringUtils.trimToEmpty("abc") = "abc"
* StringUtils.trimToEmpty(" abc ") = "abc"
* </pre>
*
* @param str 要处理的字符串
* @return 处理后的字符串;如果原字符串为 null 则返回空字符串
*/
public static String trimToEmpty(String str) {
return str == null ? EMPTY : str.trim();
}
// ---------------------- isNumeric ----------------------
/**
* 检查字符串是否只包含数字字符(Unicode 数字),不含小数点等其他符号。
*
* <p>{@code null} 返回 {@code false}。空字符串返回 {@code false}。</p>
*
* <pre>
* StringUtils.isNumeric(null) = false
* StringUtils.isNumeric("") = false
* StringUtils.isNumeric(" ") = false
* StringUtils.isNumeric("123") = true
* StringUtils.isNumeric("12 3") = false
* StringUtils.isNumeric("ab2c") = false
* StringUtils.isNumeric("12-3") = false
* StringUtils.isNumeric("12.3") = false
* </pre>
*
* @param str 需要检查的字符串,可能为 null
* @return 如果字符串只包含数字字符并且非 null,则返回 true
*/
public static boolean isNumeric(String str) {
if (isBlank(str)) {
return false;
}
int sz = str.length();
for (int i = 0; i < sz; i++) {
if (!Character.isDigit(str.charAt(i))) {
return false;
}
}
return true;
}
// ---------------------- Count matches ----------------------
/**
* 统计子字符串在主字符串中出现的次数。
*
* <p>如果任一参数为 null 或空字符串,则返回 0。</p>
*
* <pre>
* StringUtils.countMatches(null, *) = 0
* StringUtils.countMatches("", *) = 0
* StringUtils.countMatches("abba", null) = 0
* StringUtils.countMatches("abba", "") = 0
* StringUtils.countMatches("abba", "a") = 2
* StringUtils.countMatches("abba", "ab") = 1
* StringUtils.countMatches("abba", "xxx") = 0
* </pre>
*
* @param str 主字符串,可能为 null
* @param sub 子字符串,可能为 null
* @return 出现次数,如果任一参数为 null 则返回 0
*/
public static int countMatches(String str, String sub) {
if (isEmpty(str) || isEmpty(sub)) {
return 0;
}
int count = 0;
int idx = 0;
while ((idx = str.indexOf(sub, idx)) != INDEX_NOT_FOUND) {
count++;
idx += sub.length();
}
return count;
}
// ---------------------- upperCase/ lowerCase ----------------------
/**
* 将字符串的第一个字母转换为大写。
*
* <pre>
* StringUtils.upperCaseFirst(null, *) = null
* StringUtils.upperCaseFirst("", *) = ""
* StringUtils.countMatches("abc", *) = "Abc"
* </pre>
*
* @param text 输入字符串
* @return 第一个字母转为大写的字符串
*/
public static String upperCaseFirst(String text) {
if (isBlank(text)) {
return text;
}
return text.substring(0, 1).toUpperCase() + text.substring(1);
}
/**
* 将字符串的第一个字母转换为小写。
*
* <pre>
* StringUtils.lowerCaseFirst(null, *) = null
* StringUtils.lowerCaseFirst("", *) = ""
* StringUtils.lowerCaseFirst("ABC", *) = "aBC"
* </pre>
*
* @param text 输入字符串
* @return 第一个字母转为小写的字符串
*/
public static String lowerCaseFirst(String text) {
if (isBlank(text)) {
return text;
}
return text.substring(0, 1).toLowerCase() + text.substring(1);
}
/**
* 下划线命名格式转换为驼峰命名格式。
*
* <pre>
* StringUtils.lowerCaseFirst(null, *) = null
* StringUtils.lowerCaseFirst("", *) = ""
* StringUtils.lowerCaseFirst("aaa_bbb", *) = "aaaBbb"
* </pre>
*
* @param underscoreText 使用下划线分隔的字符串
* @return 转换后的驼峰命名格式字符串
*/
public static String underlineToCamelCase(String underscoreText) {
if (isBlank(underscoreText)) {
return underscoreText;
}
StringBuilder result = new StringBuilder();
boolean flag = false;
for (int i = 0; i < underscoreText.length(); i++) {
char ch = underscoreText.charAt(i);
if ("_".charAt(0) == ch) {
flag = true;
} else {
if (flag) {
result.append(Character.toUpperCase(ch));
flag = false;
} else {
result.append(ch);
}
}
}
return result.toString();
}
// ---------------------- substring ----------------------
/**
* 截取从指定位置开始到末尾的子字符串。
*
* <pre>
* StringTool.substring(null, *) = null
* StringTool.substring("", *) = ""
* StringTool.substring("abc", 0) = "abc"
* StringTool.substring("abc", 2) = "c"
* StringTool.substring("abc", 4) = ""
* StringTool.substring("abc", -2) = "abc"
* </pre>
*
* @param str 原始字符串,可能为 null
* @param start 开始截取的位置(负数表示从头开始)
* @return 截取后的子字符串
*/
public static String substring(final String str, int start) {
if (str == null) {
return null;
}
if (start < 0) {
start = 0;
}
if (start > str.length()) {
return EMPTY;
}
return str.substring(start);
}
/**
* 截取从起始位置到结束位置之间的子字符串。
*
* <pre>
* StringTool.substring(null, *, *) = null
* StringTool.substring("", * , *) = "";
* StringTool.substring("abc", 1, 2) = "b"
* StringTool.substring("abc", -1, 2) = "ab"
* StringTool.substring("abc", 1, 0) = ""
* StringTool.substring("abc", 1, 5) = "bc"
* StringTool.substring("abc", 2, 1) = ""
* </pre>
*
* @param str 原始字符串,可能为 null
* @param start 开始位置(负数表示从头开始)
* @param end 结束位置(不包括该位置,超出范围自动调整)
* @return 截取后的子字符串
*/
public static String substring(final String str, int start, int end) {
if (str == null) {
return null;
}
if (start < 0) {
start = 0;
}
if (start > str.length()) {
return EMPTY;
}
if (end > str.length()) {
end = str.length();
}
if (start > end) {
return EMPTY;
}
return str.substring(start, end);
}
// ---------------------- split、join ----------------------
/**
* 根据分隔符将字符串拆分为列表,默认去除空白项并修剪元素。
*
* <pre>
* StringTool.split("a,b,c", ",") = ["a","b","c"]
* StringTool.split("a,b,", ",") = ["a","b"]
* StringTool.split("a, ,c", ",") = ["a","c"]
* </pre>
*
* @param str 要分割的字符串
* @param separator 分隔符
* @return 拆分后的字符串列表
*/
public static List<String> split(final String str, final String separator) {
return split(str, separator, true, true);
}
/**
* 根据分隔符将字符串拆分为列表,支持控制是否修剪和忽略空白项。
*
* <pre>
* StringTool.split("a,b,c", ",") = ["a","b","c"]
* StringTool.split("a, b ,c", ",") = ["a","b","c"]
* StringTool.split("a,,c", ",") = ["a","c"]
* </pre>
*
* @param str 要分割的字符串
* @param separator 分隔符
* @param trimTokens 是否修剪每个元素
* @param ignoreBlackTokens 是否忽略空白元素
* @return 拆分后的字符串列表
*/
public static List<String> split(final String str, final String separator, boolean trimTokens, boolean ignoreBlackTokens) {
if (isBlank(str)) {
return null;
}
if (isBlank(separator)) {
return List.of(str.trim());
}
List<String> list = new ArrayList<>();
for (String item : str.split(separator)) {
if (trimTokens) {
item = item.trim();
}
if (ignoreBlackTokens && isBlank(item)) {
continue;
}
list.add(item);
}
return list;
}
/**
* 将字符串列表连接成一个字符串,默认使用逗号作为分隔符,忽略空值并修剪元素。
*
* <pre>
* StringTool.join(["a","b","c"], ",") = "a,b,c"
* StringTool.join(["a","b"," c "], ",") = "a,b,c"
* StringTool.join(["a","b",""], ",") = "a,b"
* StringTool.join(["a",null,"c"], ",") = "a,c"
* </pre>
*
* @param list 要连接的字符串列表
* @param separator 连接使用的分隔符
* @return 连接后的字符串
*/
public static String join(final List<String> list, String separator) {
return join(list, separator, true, true);
}
/**
* 将字符串列表连接成一个字符串,可配置是否修剪和忽略空值。
*
* @param list 要连接的字符串列表
* @param separator 连接使用的分隔符
* @param trimTokens 是否修剪每个元素
* @param ignoreBlackTokens 是否忽略空值元素
* @return 连接后的字符串
*/
public static String join(final List<String> list, String separator, boolean trimTokens, boolean ignoreBlackTokens) {
if (CollectionUtils.isEmpty(list)) {
return null;
}
if (separator == null) {
separator = EMPTY;
}
final StringBuilder buf = new StringBuilder();
boolean first = true;
for (String item : list) {
// parse token
if (ignoreBlackTokens && isBlank(item)) {
continue;
}
String token = trimTokens?item.trim():item;
// separator
if (first) {
first = false;
} else {
buf.append(separator);
}
// append
buf.append(token);
}
return buf.toString();
}
// ---------------------- format ----------------------
/**
* 使用参数数组格式化模板字符串。
*
* <pre>
* StringTool.format("hello,{0}!", "world")); = hello,world!
* StringTool.format("hello,{0}!", null)); = hello,{0}!
* StringTool.format("hello,{0}!")); = hello,{0}!
* StringTool.format("hello,{0}!", "world", "world")); = hello,world!
* StringTool.format("Hello {0}, welcome {1}!")); = Hello {0}, welcome {1}!
* StringTool.format("Hello {0}, welcome {1}!",null)); = Hello {0}, welcome {1}!
* StringTool.format("Hello {0}, welcome {1}!",null, null)); = Hello null, welcome null!
* StringTool.format("Hello {0}, welcome {1}!", "Alice")); = Hello Alice, welcome {1}!
* StringTool.format("Hello {0}, welcome {1}!", "Alice", "Jack")); = Hello Alice, welcome Jack!
* StringTool.format("Hello {0}, welcome {1}!", "Alice", "Jack", "Lucy")); = Hello Alice, welcome Jack!
* StringTool.format("Hello {0}, you have {1} messages", "Alice", 5)); = Hello Alice, you have 5 messages
* StringTool.format("{1} messages for {0}", "Alice", 5)); = 5 messages for Alice
* StringTool.format("Hello {0}, welcome {0}!", "Alice")); = Hello Alice, welcome Alice!
* StringTool.format("Balance: {0,number}", 1234.56)); = Balance: 1,234.56
* StringTool.format("Price: {0,number,currency}", 1234.56)); = Price: ¥1,234.56
* StringTool.format("Success rate: {0,number,percent}", 0.85)); = Success rate: 85%
* StringTool.format("Account: {0,number,#,##0.00}", 1234.5)); = Account: 1,234.50
* </pre>
*
* @param template 模板字符串
* @param params 参数数组
* @return 格式化后的字符串
*/
public static String format(String template, Object... params) {
return MessageFormat.format(template, params);
}
/**
* 使用键值映射格式化模板字符串。
* <pre>
* StringTool.formatWithMap("{name} is {age} years old", MapTool.newMap("name", "jack", "age", 18)) = jack is 18 years old
* StringTool.formatWithMap("{name} is {age} years old", null) = {name} is {age} years old
* StringTool.formatWithMap("{name} is {age} years old", MapTool.newMap("name", "jack")) = jack is {age} years old
* StringTool.formatWithMap("{name} is {age} years old", MapTool.newMap("name", "jack", "age", null)) = jack is {age} years old
* </pre>
*
* @param template 模板字符串
* @param params 键值对参数映射
* @return 替换占位符后的字符串
*/
public static String formatWithMap(String template, Map<String, Object> params) {
if (isBlank(template) || MapUtils.isEmpty(params)) {
return template;
}
for (Map.Entry<String, Object> entry : params.entrySet()) {
// null value, will not be replaced
if (entry.getValue() == null) {
continue;
}
// do replace
String oldPattern = "{" + entry.getKey() + "}";
String newPattern = entry.getValue().toString();
template = replace(template, oldPattern, newPattern);
}
return template;
}
// ---------------------- replace ----------------------
/**
* 替换字符串中的所有匹配项。
*
* <pre>
* StringTool.replace("hello jack, how are you", "jack", "lucy")); = hello lucy, how are you
* StringTool.replace("hello jack, how are you, jack", "jack", "lucy")); = hello lucy, how are you, lucy
* StringTool.replace("", "jack", "lucy")); =
* StringTool.replace(null, "jack", "lucy")); = null
* StringTool.replace("hello jack, how are you", null, "jack")); = hello jack, how are you
* StringTool.replace("hello jack, how are you", "", "jack")); = hello jack, how are you
* StringTool.replace("hello jack, how are you", " ", "-")); = hello-jack,-how-are-you
* StringTool.replace("hello jack, how are you", "jack", null)); = hello jack, how are you
* StringTool.replace("hello jack, how are you", "jack", "")); = hello , how are you
* StringTool.replace("hello jack, how are you", "jack", " ")); = hello , how are you
* </pre>
*
* @param inString 输入字符串
* @param oldPattern 要被替换的旧模式
* @param newPattern 新模式,用于替换旧模式
* @return 替换完成的新字符串
*/
public static String replace(String inString, String oldPattern, String newPattern) {
if (isEmpty(inString) || isEmpty(oldPattern) || newPattern == null) {
return inString;
}
return inString.replace(oldPattern, newPattern);
}
// ---------------------- remove prefix、suffix ----------------------
/**
* 移除字符串开头的前缀。
*
* <pre>
* StringTool.removePrefix("hello,world", "hello") = ,world
* StringTool.removePrefix("hello,world", "world") = hello,world
* StringTool.removePrefix("hello,world", "hello,world") =
* StringTool.removePrefix("hello,world", "") = hello,world
* StringTool.removePrefix("hello,world", null) = hello,world
* StringTool.removePrefix("", "world") =
* StringTool.removePrefix(null, "world") = null
* </pre>
* @param str 目标字符串
* @param prefix 要移除的前缀
* @return 移除前缀后的字符串
*/
public static String removePrefix(String str, String prefix) {
if (str == null || StringUtils.isBlank(prefix)) {
return str;
}
if (str.startsWith(prefix)) {
return str.substring(prefix.length());
}
return str;
}
/**
* 移除字符串结尾的后缀。
*
* <pre>
* StringTool.removeSuffix("hello,world", "hello") hello,world
* StringTool.removeSuffix("hello,world", "world") hello,
* StringTool.removeSuffix("hello,world", "hello,world"))
* StringTool.removeSuffix("hello,world", "") hello,world
* StringTool.removeSuffix("hello,world", null) hello,world
* StringTool.removeSuffix("", "world")
* StringTool.removeSuffix(null, "world") null
* </pre>
* @param str 目标字符串
* @param suffix 要移除的后缀
* @return 移除后缀后的字符串
*/
public static String removeSuffix(String str, String suffix) {
if (str == null || StringUtils.isBlank(suffix)) {
return str;
}
if (str.endsWith(suffix)) {
return str.substring(0, str.length() - suffix.length());
}
return str;
}
// ---------------------- other ----------------------
/**
* 安全地比较两个字符串是否相等。
*
* <pre>
* StringTool.equals("hello", "hello") = true
* StringTool.equals("hello", "world") = false
* StringTool.equals(null, null) = true
* StringTool.equals(null, "world") = false
* StringTool.equals("hello", null) = false
* </pre>
*
* @param str1 第一个字符串
* @param str2 第二个字符串
* @return 如果两个字符串相等则返回 true,否则返回 false
*/
public static boolean equals(String str1, String str2) {
return str1 == null ? str2 == null : str1.equals(str2);
}
// ---------------------- other ----------------------
}
最后修改:2025 年 11 月 14 日
© 允许规范转载