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 日
如果觉得我的文章对你有用,请随意赞赏