All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.feilong.core.lang.StringUtil Maven / Gradle / Ivy

Go to download

feilong is a suite of core and expanded libraries that include utility classes, http, excel,cvs, io classes, and much much more.

There is a newer version: 4.3.0
Show newest version
/*
 * Copyright (C) 2008 feilong
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.feilong.core.lang;

import static com.feilong.core.CharsetType.UTF8;
import static com.feilong.core.Validator.isNullOrEmpty;
import static com.feilong.core.bean.ConvertUtil.convert;
import static com.feilong.core.bean.ConvertUtil.toArray;
import static com.feilong.core.bean.ConvertUtil.toList;
import static com.feilong.core.bean.ConvertUtil.toSet;
import static com.feilong.core.lang.ArrayUtil.EMPTY_STRING_ARRAY;
import static com.feilong.core.util.CollectionsUtil.newArrayList;
import static com.feilong.core.util.CollectionsUtil.size;
import static com.feilong.core.util.MapUtil.newHashMap;
import static java.util.Collections.emptyMap;

import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

import org.slf4j.helpers.FormattingTuple;
import org.slf4j.helpers.MessageFormatter;

import com.feilong.core.CharsetType;
import com.feilong.core.Validate;
import com.feilong.core.bean.ConvertUtil;
import com.feilong.core.util.RegexUtil;
import com.feilong.lib.lang3.StringUtils;
import com.feilong.lib.lang3.text.StrSubstitutor;

/**
 * {@link String}工具类,可以查询,截取,format.
 * 
 * 

分隔(split)

* *
*
    *
  • {@link #split(String, String)}
  • *
*
* *

分隔(tokenize)

* *
*
    *
  • {@link #tokenizeToStringArray(String, String)}
  • *
  • {@link #tokenizeToStringArray(String, String, boolean, boolean)}
  • *
* *

* 区别在于,split 使用的是 正则表达式 {@link Pattern#split(CharSequence)} 分隔(特别注意,一些特殊字符 $|()[{^?*+\\ 需要转义才能做分隔符),而 {@link StringTokenizer} 使用索引机制,在性能上 * StringTokenizer更高
* 因此,在注重性能的场景,还是建议使用{@link StringTokenizer} *

* *
* *

查询(search)

* *
*
    *
  • {@link StringUtils#countMatches(CharSequence, CharSequence)} 查询出现的次数
  • *
*
* *

其他

* *
*
    *
  • {@link StringUtils#capitalize(String)} 首字母大写
  • *
  • {@link StringUtils#uncapitalize(String)} 单词首字母小写
  • *
  • org.apache.commons.lang3.text.WordUtils#uncapitalize(String, char...) 如果要使用一段文字,每个单词首字母小写
  • *
*
* *

{@link String#String(byte[] )} 和 {@link String#String(byte[], Charset)} 区别

* *
*

* {@link String#String(byte[] )} 其实调用了{@link String#String(byte[], Charset)};
* 先使用 {@link Charset#defaultCharset()},如果有异常再用 ISO-8859-1, 具体参见 java.lang.StringCoding#decode(byte[], int, int) *

*
* *

{@link StringBuffer} 和 {@link StringBuilder} 和 {@link String} 对比

* *
*
    *
  • {@link StringBuffer} 字符串变量(线程安全)
  • *
  • {@link StringBuilder} 字符串变量(非线程安全)
  • *
  • {@link String} 字符串常量
  • *
  • 在大部分情况下 {@link StringBuffer} {@code >} {@link String}
  • *
  • 在大部分情况下 {@link StringBuilder} {@code >} {@link StringBuffer}
  • *
*
* *

String s1 = new String("xyz"); 到底创建几个对象?

* *
*

* 要看虚拟机的实现.而且要联系上下文 *

* *
    *
  • 假设:HotSpot1.6,之前没有创建过"xyz" 则创建2个,之前创建过"xyz"则只创建1个
  • *
  • 假设:HotSpot1.7,之前不管有没有创建过"xyz" 都创建1个
  • *
*
* *

String s3 = s1 + s2; 
* s3.intern() == s3 到底相不相等?

*

* 要看虚拟机的实现 *

* *
    *
  • 假设:HotSpot1.6,则false不相等
  • *
  • 假设:HotSpot1.7,则在之前没有创建过"abcabc"时,true相等
  • *
*
* *

{@link StringUtil#replace(String, String, String) replace} 和 {@link #replaceAll(CharSequence, String, String) replaceAll} 和 * {@link String#replaceFirst(String, String) replaceFirst}区别:

* *
* * * * * * * * * * * * * * * * * * *
字段说明
{@link StringUtil#replace(String, String, String)}将字符串中出现的target替换成replacement
{@link #replaceAll(CharSequence, String, String)}regex是一个正则表达式,将字符串中匹配的子字符串替换为replacement
{@link String#replaceFirst(String, String)}和{@link StringUtil#replace(String, String, String)}类似,只不过只替换第一个出现的地方。
* *

* 对比以下代码: *

* *
 * StringUtil.replaceAll("SH1265,SH5951", "([a-zA-Z]+[0-9]+)", "'$1'")  ='SH1265','SH5951'
 * StringUtil.replace("SH1265,SH5951", "([a-zA-Z]+[0-9]+)", "'$1'")     =SH1265,SH5951
 * "SH1265,SH5951".replaceFirst("([a-zA-Z]+[0-9]+)", "'$1'")            ='SH1265',SH5951
 * 
* *
* * @author feilong * @see java.util.StringTokenizer * @see "org.springframework.util.StringUtils#tokenizeToStringArray(String, String)" * @see "org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#MULTI_VALUE_ATTRIBUTE_DELIMITERS" * @see com.feilong.lib.lang3.StringUtils * @see "com.google.common.base.Strings" * @since 1.4.0 */ public final class StringUtil{ /** * A String for a space character. * * @since 3.0.0 */ public static final String SPACE = " "; /** * The empty String {@code ""}. * * @since 3.0.0 */ public static final String EMPTY = ""; /** * A String for linefeed LF ("\n"). * * @see JLF: Escape Sequences * for Character and String Literals * @since 3.0.0 */ public static final String LF = "\n"; /** * A String for carriage return CR ("\r"). * * @see JLF: Escape Sequences * for Character and String Literals * @since 3.0.0 */ public static final String CR = "\r"; //--------------------------------------------------------------- /** Don't let anyone instantiate this class. */ private StringUtil(){ //AssertionError不是必须的. 但它可以避免不小心在类的内部调用构造器. 保证该类在任何情况下都不会被实例化. //see 《Effective Java》 2nd throw new AssertionError("No " + getClass().getName() + " instances for you!"); } //--------------------------------------------------------------- /** * Constructs a new String by decoding the specified array of bytes using the given charset. * *

* 默认使用 {@link CharsetType#UTF8} *

* * @param bytes * The bytes to be decoded into characters, may be null * @return A new String decoded from the specified array of bytes using the given charset, * or null if the input byte array was null. * @see String#String(byte[], String) * @see "org.apache.commons.lang3.StringUtils#toString(byte[], String)" * @see com.feilong.lib.lang3.StringUtils#toEncodedString(byte[], Charset) * @see "org.apache.commons.codec.binary.StringUtils#newString(byte[], String)" * @since 3.3.2 */ public static String newString(byte[] bytes){ return newString(bytes, UTF8); } /** * Constructs a new String by decoding the specified array of bytes using the given charset. * * @param bytes * The bytes to be decoded into characters, may be null * @param charsetType * 字符编码,建议使用 {@link CharsetType} 定义好的常量 * @return A new String decoded from the specified array of bytes using the given charset, * or null if the input byte array was null. * @see String#String(byte[], String) * @see "org.apache.commons.lang3.StringUtils#toString(byte[], String)" * @see com.feilong.lib.lang3.StringUtils#toEncodedString(byte[], Charset) * @see "org.apache.commons.codec.binary.StringUtils#newString(byte[], String)" * @since 1.3.0 */ public static String newString(byte[] bytes,String charsetType){ return StringUtils.toEncodedString(bytes, Charset.forName(charsetType)); } // [start]toBytes //--------------------------------------------------------------- /** * 字符串转换成byte数组. * * @param value * 字符串 * @return 如果 value 是null,抛出 {@link NullPointerException}
* @see String#getBytes() * @since 1.3.0 */ public static byte[] getBytes(String value){ Validate.notNull(value, "value can't be null!"); return value.getBytes(); } /** * 字符串 value 转换成byte数组. * * @param value * 字符串 * @param charsetName * 受支持的 charset 名称,比如 utf-8, {@link CharsetType} * @return 如果 value 是null,抛出 {@link NullPointerException}
* 如果 charsetName 是null,抛出 {@link NullPointerException}
* 如果 charsetName 是blank,抛出 {@link IllegalArgumentException}
* @see String#getBytes(String) * @since 1.3.0 */ public static byte[] getBytes(String value,String charsetName){ Validate.notNull(value, "value can't be null!"); Validate.notBlank(charsetName, "charsetName can't be blank!"); //--------------------------------------------------------------- try{ return value.getBytes(charsetName); }catch (UnsupportedEncodingException e){ String pattern = "value:[{}],charsetName:[{}],suggest you use [{}] constants"; String message = StringUtil.formatPattern(pattern, value, charsetName, CharsetType.class.getCanonicalName()); throw new UncheckedIOException(message, e); } } // [end] // [start]replace //--------------------------------------------------------------- /** * 将 text 中的 searchString 替换成 replacement. * *

示例:

* *
* *

* A {@code null} reference passed to this method is a no-op. *

* *
     * StringUtil.replace(null, *, *) = null
     * StringUtil.replace("", *, *) = ""
     * StringUtil.replace("any", null, *) = "any"
     * StringUtil.replace("any", *, null) = "any"
     * StringUtil.replace("any", "", *) = "any"
     * StringUtil.replace("aba", "a", null) = "aba"
     * StringUtil.replace("aba", "a", "") = "b"
     * StringUtil.replace("aba", "a", "z") = "zbz"
     * 
     * StringUtil.replace("黑色/黄色/蓝色", "/", "_")         =   "黑色_黄色_蓝色"
     * StringUtil.replace(null, "/", "_")               =   null
     * StringUtil.replace("黑色/黄色/蓝色", "/", null)        =   "黑色/黄色/蓝色"
     * 
* * 此外注意的是: * *
     * StringUtil.replace("SH1265,SH5951", "([a-zA-Z]+[0-9]+)", "'$1'") = SH1265,SH5951
     * 
* * (注意和 {@link #replaceAll(CharSequence, String, String)} 的区别) * *
* *

注意:

*
*
    *
  1. 该替换从字符串的开头朝末尾执行,例如,用 "b" 替换字符串 "aaa" 中的 "aa" 将生成 "ba" 而不是 "ab".
  2. *
  3. 虽然底层调用了{@link java.util.regex.Matcher#replaceAll(String) Matcher.replaceAll()},但是使用了 * {@link java.util.regex.Matcher#quoteReplacement(String) Matcher.quoteReplacement()} 处理了特殊字符
  4. *
*
* * @param text * text to search and replace in, may be null * @param searchString * the String to search for, may be null * @param replacement * the String to replace it with, may be null * @return 如果 text 是null,返回 null
* 如果 searchString 是null,原样返回 text
* 如果 replacement 是null,原样返回 text
* @see java.lang.String#replace(CharSequence, CharSequence) * @see com.feilong.lib.lang3.StringUtils#replace(String, String, String) * @since jdk 1.5 */ public static String replace(final String text,final String searchString,final String replacement){ return StringUtils.replace(text, searchString, replacement); } /** * 使用给定的replacement替换 content 此字符串所有匹配给定的正则表达式 regex的子字符串. * *

说明:

*
*
    *
  1. * 此方法底层调用的是 {@link java.util.regex.Matcher#replaceAll(String)},相同于 * Pattern.compile(regex).matcher(str).replaceAll(repl) *
  2. *
*
* *

示例:

* *
* *
     * StringUtil.replaceAll("SH1265,SH5951,SH6766,SH7235,SH1265,SH5951,SH6766,SH7235", "([a-zA-Z]+[0-9]+)", "'$1'")
     * 
* * 返回: * *
     * 'SH1265','SH5951','SH6766','SH7235','SH1265','SH5951','SH6766','SH7235'
     * 
* *
* *

请注意:

* *
* *

* 在替代字符串replacement中,使用 backslashes反斜杆(\)和 dollar signs美元符号 ($)与将其视为字面值替代字符串所得的结果可能不同. *
* 请参阅 {@link java.util.regex.Matcher#replaceAll Matcher.replaceAll};如有需要,可使用 {@link java.util.regex.Matcher#quoteReplacement * Matcher.quoteReplacement}取消这些字符的特殊含义 *

* *

* $ may be treated as references to captured subsequences as described above,$这个特殊的字符,因为替换串使用这个引用正则表达式匹配的组, * $0代表匹配项,$1代表第1个匹配分组,$1代表第2个匹配分组 *
* 并且 \ are used to escape literal characters in the replacement string.  *

*
* *

对于以下代码:

* *
* *
     * 
     * //分隔字符串并添加单引号.
     * public void splitAndAddYinHao(){
     *     String a = "12345,56789,1123456";
     *     String[] aStrings = a.split(",");
     *     StringBuilder sb = new StringBuilder();
     *     int size = aStrings.length;
     *     for (int i = 0; i {@code <} size; i++){
     *         sb.append("'" + aStrings[i] + "'");
     *         if (i != size - 1){
     *             sb.append(",");
     *         }
     *     }
     *     LOGGER.debug(sb.toString());
     * }
     * 
     * 
* * 可以重构成: * *
     * StringUtil.replaceAll("12345,56789,1123456", "([0-9]+)", "'$1'")
     * 
* * 结果都是: * *
     * '12345','56789','1123456'
     * 
* *
* * @param content * 需要被替换的字符串 * @param regex * 用来匹配此字符串的正则表达式,规则参见 RegexPattern 注释 * @param replacement * 用来替换每个匹配项的字符串 * @return 如果 content 是null,返回 {@link StringUtils#EMPTY}
* 如果 content 中,没有 regex匹配的字符串或者格式,返回content
* @see String字符串替换的一个诡异问题 * @see Java中String对象的replaceAll方法调用性能优化小技巧 * @see java.lang.String#replaceAll(String, String) * @since jdk 1.4 */ public static String replaceAll(CharSequence content,String regex,String replacement){ // return null == content ? EMPTY : content.toString().replaceAll(regex, replacement); if (null == content){ return EMPTY; } Pattern pattern = RegexUtil.buildPattern(regex, 0); return pattern.matcher(content).replaceAll(replacement); } /** * 使用给定的字符串 templateString 作为模板,解析匹配的变量 . * *

示例:

* *
* *
     * String template = "/home/webuser/expressdelivery/${yearMonth}/${expressDeliveryType}/vipQuery_${fileName}.log";
     * Date date = now();
     * 
     * Map{@code } valuesMap = newHashMap();
     * valuesMap.put("yearMonth", DateUtil.toString(date, DatePattern.YEAR_AND_MONTH));
     * valuesMap.put("expressDeliveryType", "sf");
     * valuesMap.put("fileName", DateUtil.toString(date, DatePattern.TIMESTAMP));
     * LOGGER.debug(StringUtil.replace(template, valuesMap));
     * 
* * 返回: * *
     * /home/webuser/expressdelivery/2016-06/sf/vipQuery_20160608214846.log
     * 
* *
* *

* 注意:此方法只能替换字符串,而不能像el表达式一样使用对象属性之类的来替换 *

* *

比如:

* *
* *
     * Map{@code } valuesMap = newHashMap();
     * valuesMap.put("today", DateUtil.toString(now(), COMMON_DATE));
     * valuesMap.put("user", new User(1L));
     * LOGGER.debug(StringUtil.replace("${today}${today1}${user.id}${user}", valuesMap) + "");
     * 
* * 返回: * *
     * 2016-07-16${today1}${user.id}com.feilong.test.User@16f9a31
     * 
* *
* * @param * the value type * @param templateString * the template string * @param valuesMap * the values map * @return 如果 templateStringStringUtils.isEmpty(templateString),返回 {@link StringUtils#EMPTY}
* 如果 valuesMap 是null或者empty,原样返回 templateString
* @see com.feilong.lib.lang3.text.StrSubstitutor#replace(String) * @see com.feilong.lib.lang3.text.StrSubstitutor#replace(Object, Map) * @since 1.1.1 */ public static String replace(CharSequence templateString,Map valuesMap){ return StringUtils.isEmpty(templateString) ? EMPTY : StrSubstitutor.replace(templateString, valuesMap); } // [end] // [start]substring //--------------------------------------------------------------- /** * [截取]从指定索引处(beginIndex)的字符开始,直到此字符串末尾. * *

* 如果 beginIndex是负数,那么表示倒过来截取,从结尾开始截取长度,此时等同于 {@link #substringLast(String, int)} *

* *
     * StringUtil.substring(null, *)   = null
     * StringUtil.substring("", *)     = ""
     * StringUtil.substring("abc", 0)  = "abc"
     * StringUtil.substring("abc", 2)  = "c"
     * StringUtil.substring("abc", 4)  = ""
     * StringUtil.substring("abc", -2) = "bc"
     * StringUtil.substring("abc", -4) = "abc"
     * StringUtil.substring("jinxin.feilong",6)    =.feilong
     * 
* * @param text * 内容 the String to get the substring from, may be null * @param beginIndex * 从指定索引处 the position to start from,negative means count back from the end of the String by this many characters * @return 如果 text 是null,返回 null
* An empty ("") String 返回 "".
* @see com.feilong.lib.lang3.StringUtils#substring(String, int) * @see #substringLast(String, int) */ public static String substring(final String text,final int beginIndex){ return StringUtils.substring(text, beginIndex); } /** * [截取]从开始位置(startIndex),截取固定长度(length)字符串. * *
     * StringUtil.substring(null, 6, 8)                 =   null
     * StringUtil.substring("jinxin.feilong", 6, 2)     =   .f
     * 
* * @param text * 被截取文字 * @param startIndex * 索引开始位置,0开始 * @param length * 长度 {@code >=1} * @return 如果 text 是null,返回 null
* 如果 startIndex + length {@code >} text.length,那么截取 从 startIndex 开始截取,截取到最后 * @see com.feilong.lib.lang3.StringUtils#substring(String, int, int) */ public static String substring(final String text,int startIndex,int length){ return StringUtils.substring(text, startIndex, startIndex + length); } /** * [截取]:获取文字最后位数 lastLenth 的字符串. * *

示例:

* *
* *
     * StringUtil.substringLast("jinxin.feilong", 5) = ilong
     * 
* *
* * @param text * 文字 * @param lastLenth * 最后的位数 * @return 如果 text 是null,返回 null
* 如果 {@code lastLenth<0},返回 {@link StringUtils#EMPTY}
* 如果 {@code text.length() <= lastLenth},返回text
* 否则返回 text.substring(text.length() - lastLenth) * @see com.feilong.lib.lang3.StringUtils#right(String, int) */ public static String substringLast(final String text,int lastLenth){ return StringUtils.right(text, lastLenth); } /** * [截取]:去除最后几位 lastLenth . * *

* 调用了 {@link java.lang.String#substring(int, int)} *

* *

示例:

* *
* *
     * StringUtil.substringWithoutLast("jinxin.feilong", 5) //jinxin.fe
     * 
* *
* * @param text * 文字 * @param lastLenth * 最后的位数 * @return 如果 text 是null,返回 {@link StringUtils#EMPTY}
* @see java.lang.String#substring(int, int) * @see com.feilong.lib.lang3.StringUtils#left(String, int) */ public static String substringWithoutLast(final String text,final int lastLenth){ return null == text ? EMPTY : text.substring(0, text.length() - lastLenth); } /** * [截取]:去除最后的字符串 lastString. * *

示例:

* *
* *
     * StringUtil.substringWithoutLast(null, "222")                     = ""
     * StringUtil.substringWithoutLast("jinxin.feilong", "ng")          = "jinxin.feilo"
     * StringUtil.substringWithoutLast("jinxin.feilong     ", "     ")  = "jinxin.feilong"
     * 
* *
* * @param text * the text * @param lastString * the last string * @return 如果 text 是null,返回 {@link StringUtils#EMPTY}
* 如果 lastString 是null,返回 text.toString()
* @since 1.4.0 */ public static String substringWithoutLast(final CharSequence text,final String lastString){ if (null == text){ return EMPTY; } String textString = text.toString(); if (null == lastString){ return textString; } return textString.endsWith(lastString) ? substringWithoutLast(textString, lastString.length()) : textString; } // [end] // [start]splitToT /** * 将字符串 value 使用分隔符 regexSpliter 分隔成 字符串数组. * *

* 建议使用 {@link #tokenizeToStringArray(String, String)} 或者 {@link StringUtils#split(String)} *

* * @param value * value * @param regexSpliter * 此处不是简单的分隔符,是正则表达式,.$|()[{^?*+\\ 有特殊的含义,因此我们使用.的时候必须进行转义,"\"转义时要写成"\\\\"
* 最终调用了 {@link java.util.regex.Pattern#split(CharSequence)} * @return 如果 value 是null或者empty,返回 {@link ArrayUtil#EMPTY_STRING_ARRAY}
* @see String#split(String) * @see String#split(String, int) * @see StringUtils#split(String) * @see java.util.regex.Pattern#split(CharSequence) */ public static String[] split(String value,String regexSpliter){ return isNullOrEmpty(value) ? EMPTY_STRING_ARRAY : value.split(regexSpliter); } // [end] // [start]tokenizeToStringArray /** * 使用默认分隔符 ;, (逗号,分号和空格) 分隔给定的字符串到字符串数组,去除 tokens的空格并且忽略empty tokens. * *

* (此方法借鉴 "org.springframework.util.StringUtils#tokenizeToStringArray"). *

* *

* 调用了 {@link #tokenizeToStringArray(String, String, boolean, boolean)},
* 本方法默认使用参数 trimTokens = true;
* ignoreEmptyTokens = true;,
* 默认分隔符 ;, (逗号,分号和空格) *

* *

示例:

*
* *
     * String str = "jin.xin  feilong ,jinxin;venusdrogon;jim ";
     * String[] tokenizeToStringArray = StringUtil.tokenizeToStringArray(str);
     * LOGGER.info(JsonUtil.format(tokenizeToStringArray));
     * 
* * 返回: * *
     * [
     * "jin.xin",
     * "feilong",
     * "jinxin",
     * "venusdrogon",
     * "jim"
     * ]
     * 
* *
* * @param str * 需要被分隔的字符串 * @return 如果 str 是null,返回 {@link ArrayUtil#EMPTY_STRING_ARRAY}
* 如果 str 是blank,返回 {@link ArrayUtil#EMPTY_STRING_ARRAY}
* @see java.util.StringTokenizer * @see String#trim() * @see "org.springframework.util.StringUtils#delimitedListToStringArray" * @see "org.springframework.util.StringUtils#tokenizeToStringArray" * * @see #tokenizeToStringArray(String, String, boolean, boolean) * @since 3.5.0 */ public static String[] tokenizeToStringArray(String str){ String delimiters = ";, "; return tokenizeToStringArray(str, delimiters); } /** * 使用默认分隔符 ;, (逗号,分号和空格) 分隔给定的字符串,转成Set,去除 tokens的空格并且忽略empty tokens. * *

* (此方法借鉴 "org.springframework.util.StringUtils#tokenizeToStringArray"). *

* *

* 调用了 {@link #tokenizeToStringArray(String, String, boolean, boolean)},
* 本方法默认使用参数 trimTokens = true;
* ignoreEmptyTokens = true;,
* 默认分隔符 ;, (逗号,分号和空格) *

* *

示例:

*
* *
     * String str = "jin.xin  feilong ,jinxin;venusdrogon;jim ";
     * String[] set = StringUtil.tokenizeToSet(str);
     * LOGGER.info(JsonUtil.format(set));
     * 
* * 返回: * *
     * [
     * "jin.xin",
     * "feilong",
     * "jinxin",
     * "venusdrogon",
     * "jim"
     * ]
     * 
* *
* * @param str * 需要被分隔的字符串 * @return 如果 str 是null或者empty,返回 {@link Collections#emptySet()}
* @see java.util.StringTokenizer * @see String#trim() * @see "org.springframework.util.StringUtils#delimitedListToStringArray" * @see "org.springframework.util.StringUtils#tokenizeToStringArray" * * @see #tokenizeToStringArray(String, String, boolean, boolean) * @since 3.5.0 */ public static Set tokenizeToSet(String str){ String[] stringArray = tokenizeToStringArray(str); return toSet(stringArray); } /** * 使用默认分隔符 ;, (逗号,分号和空格) 分隔给定的字符串,转成List,去除 tokens的空格并且忽略empty tokens. * *

* (此方法借鉴 "org.springframework.util.StringUtils#tokenizeToStringArray"). *

* *

* 调用了 {@link #tokenizeToStringArray(String, String, boolean, boolean)},
* 本方法默认使用参数 trimTokens = true;
* ignoreEmptyTokens = true;,
* 默认分隔符 ;, (逗号,分号和空格) *

* *

示例:

*
* *
     * String str = "jin.xin  feilong ,jinxin;venusdrogon;jim ";
     * String[] list = StringUtil.tokenizeToList(str);
     * LOGGER.info(JsonUtil.format(list));
     * 
* * 返回: * *
     * [
     * "jin.xin",
     * "feilong",
     * "jinxin",
     * "venusdrogon",
     * "jim"
     * ]
     * 
* *
* * @param str * 需要被分隔的字符串 * @return 如果 str 是null或者empty,返回 {@link Collections#emptyList()}
* @see java.util.StringTokenizer * @see String#trim() * @see "org.springframework.util.StringUtils#delimitedListToStringArray" * @see "org.springframework.util.StringUtils#tokenizeToStringArray" * * @see #tokenizeToStringArray(String, String, boolean, boolean) * @since 3.5.0 */ public static List tokenizeToList(String str){ String[] stringArray = tokenizeToStringArray(str); return toList(stringArray); } /** * 使用StringTokenizer分隔给定的字符串到字符串数组,去除 tokens的空格并且忽略empty tokens. * *

* (此方法借鉴 "org.springframework.util.StringUtils#tokenizeToStringArray"). *

* *

* 调用了 {@link #tokenizeToStringArray(String, String, boolean, boolean)},
* 本方法默认使用参数 trimTokens = true;
* ignoreEmptyTokens = true; *

* *

示例:

*
* *
     * String str = "jin.xin  feilong ,jinxin;venusdrogon;jim ";
     * String delimiters = ";, .";
     * String[] tokenizeToStringArray = StringUtil.tokenizeToStringArray(str, delimiters);
     * LOGGER.info(JsonUtil.format(tokenizeToStringArray));
     * 
* * 返回: * *
     * [
     * "jin",
     * "xin",
     * "feilong",
     * "jinxin",
     * "venusdrogon",
     * "jim"
     * ]
     * 
* *
* * * * @param str * 需要被分隔的字符串 * @param delimiters * delimiter characters, assembled as String
* 参数中的所有字符都是分隔标记的分隔符,比如这里可以设置成 ";, " ,spring就是使用这样的字符串来分隔数组/集合的 * @return 如果 str 是null,返回 {@link ArrayUtil#EMPTY_STRING_ARRAY}
* 如果 str 是blank,返回 {@link ArrayUtil#EMPTY_STRING_ARRAY}
* @see java.util.StringTokenizer * @see String#trim() * @see "org.springframework.util.StringUtils#delimitedListToStringArray" * @see "org.springframework.util.StringUtils#tokenizeToStringArray" * * @see #tokenizeToStringArray(String, String, boolean, boolean) * @since 1.0.7 * @apiNote *

说明:

* *
*

* 给定的delimiters字符串支持任意数量的分隔字符characters.
* 每一个characters可以用来分隔tokens.一个delimiter分隔符常常是一个single字符;
* 如果你要使用多字符 multi-character delimiters分隔, 你可以考虑使用delimitedListToStringArray *

*
*/ public static String[] tokenizeToStringArray(String str,String delimiters){ boolean trimTokens = true; boolean ignoreEmptyTokens = true; return tokenizeToStringArray(str, delimiters, trimTokens, ignoreEmptyTokens); } /** * 使用StringTokenizer分隔给定的字符串到字符串数组. * *

* (此方法借鉴 "org.springframework.util.StringUtils#tokenizeToStringArray"). *

* *

说明:

* *
*

* 给定的delimiters字符串支持任意数量的分隔字符characters.
* 每一个characters可以用来分隔tokens.一个delimiter分隔符常常是一个single字符;
* 如果你要使用多字符 multi-character delimiters分隔, 你可以考虑使用delimitedListToStringArray *

*
* *

关于 {@link StringTokenizer}:

* *
* * {@link StringTokenizer} implements {@code Enumeration}
* 其在 Enumeration接口的基础上,定义了 hasMoreTokens nextToken两个方法
* 实现的Enumeration接口中的 hasMoreElements nextElement,调用了 hasMoreTokens nextToken
* * * * @param str * 需要被分隔的字符串 * @param delimiters * delimiter characters, assembled as String
* 参数中的所有字符都是分隔标记的分隔符,比如这里可以设置成 ";, " ,spring就是使用这样的字符串来分隔数组/集合的 * @param trimTokens * 是否使用 {@link String#trim()}操作token * @param ignoreEmptyTokens * 是否忽视空白的token,如果为true,那么token必须长度 {@code >} 0;如果为false会包含长度=0 空白的字符
* (仅仅用于那些 trim之后是empty的tokens,StringTokenizer不会考虑subsequent delimiters as token in the first place). * @return 如果 str 是null,返回 {@link ArrayUtil#EMPTY_STRING_ARRAY}
* @see java.util.StringTokenizer * @see String#trim() * @see "org.springframework.util.StringUtils#delimitedListToStringArray" * @see "org.springframework.util.StringUtils#tokenizeToStringArray" * @since 1.0.7 */ public static String[] tokenizeToStringArray(String str,String delimiters,boolean trimTokens,boolean ignoreEmptyTokens){ if (null == str){ return EMPTY_STRING_ARRAY; } //--------------------------------------------------------------- List tokenList = newArrayList(); StringTokenizer stringTokenizer = new StringTokenizer(str, delimiters); while (stringTokenizer.hasMoreTokens()){ String token = stringTokenizer.nextToken(); token = trimTokens ? token.trim() : token;//去空 if (!ignoreEmptyTokens || token.length() > 0){ tokenList.add(token); } } return toArray(tokenList, String.class); } // [end] /** * * 将配置类型的逗号分隔字符串分割转换成数组,然后自动转换成element一样的类型,判断是否包含. * *

示例:

* *
* *
     * 
     * StringUtil.tokenizeToArrayContains((String) null, null) = false;
     * StringUtil.tokenizeToArrayContains(null, EMPTY) = false;
     * StringUtil.tokenizeToArrayContains(EMPTY, null) = false;
     * StringUtil.tokenizeToArrayContains(EMPTY, EMPTY) = false;
     * StringUtil.tokenizeToArrayContains("   ", EMPTY) = false;
     * 
     * StringUtil.tokenizeToArrayContains("1,2,2,,3,4", "3") = true;
     * StringUtil.tokenizeToArrayContains("1,2,2,,3,4", 3) = true;
     * StringUtil.tokenizeToArrayContains("1,2,2,3,4", "1") = true;
     * StringUtil.tokenizeToArrayContains("1,2,2,3,4", 1) = true;
     * StringUtil.tokenizeToArrayContains("1,2,2,3,4", 1L) = true;
     * StringUtil.tokenizeToArrayContains("1,2,2,3,4", toBigDecimal(1)) = true;
     * StringUtil.tokenizeToArrayContains("1,2,2,3,4", Double.valueOf(1)) = true;
     * 
     * 
* *
* *

重构:

* *
*

* 对于以下代码: *

* *
     * String redMultiBookIds = staticConfig.getRedMultiBookIds();
     * if (isNullOrEmpty(redMultiBookIds)){
     *     return 0;
     * }
     * 
     * String[] tokenizeToStringArray = StringUtil.tokenizeToStringArray(redMultiBookIds, ",");
     * boolean contains = Arrays.asList(tokenizeToStringArray).contains("" + entryId);
     * return contains ? 1 : 0;
     * 
* * 可以重构成: * *
     * String redMultiBookIds = staticConfig.getRedMultiBookIds();
     * if (isNullOrEmpty(redMultiBookIds)){
     *     return 0;
     * }
     * 
     * boolean contains = StringUtil.tokenizeToStringArrayContains(redMultiBookIds, entryId);
     * return contains ? 1 : 0;
     * 
* *
* * @param * the generic type * @param config * the config * @param element * the element * @return 如果 config 是null或者empty,返回false
* 如果 element 是null,返回false
* 否则将config 使用逗号分隔成字符串数组,然后逐个转换成element类型,如果相等,返回true ,否则false * @since 3.3.7 */ public static boolean tokenizeToArrayContains(String config,T element){ return tokenizeToArrayContains(config, ",", element); } /** * 将配置类型的逗号分隔字符串分割转换成数组,然后自动转换成element一样的类型,判断是否包含. * * @param * the generic type * @param config * the config * @param delimiters * the delimiters * @param element * the element * @return 如果 config 是null或者empty,返回false
* 如果 element 是null,返回false
* 否则将config 使用逗号分隔成字符串数组,然后逐个转换成element类型,如果相等,返回true ,否则false * @since 3.3.7 */ public static boolean tokenizeToArrayContains(String config,String delimiters,T element){ if (isNullOrEmpty(config)){ return false; } if (null == element){ return false; } //--------------------------------------------------------------- String[] array = StringUtil.tokenizeToStringArray(config, delimiters); if (isNullOrEmpty(array)){ return false; } //--------------------------------------------------------------- Class klass = (Class) element.getClass(); for (String str : array){ T convertValue = ConvertUtil.convert(str, klass); if (Objects.equals(convertValue, element)){ return true; } } return false; } /** * 将 189=988;200=455;这种格式的字符串转换成map , map的key 是189这种, value 是988,455 这种等号后面的值,使用逗号分隔成list. * *

示例:

* *
* *
     * 
     * Map map = StringUtil.toSingleValueMap("1=2;89=100;200=230");
     * 
     * assertThat(
     *                 map,
     *                 allOf(//
     *                                 hasEntry("1", "2"),
     *                                 hasEntry("89", "100"),
     *                                 hasEntry("200", "230")));
     * 
     * 
* * *
* * @param config * 189=988;200=455; 这种格式的配置字符串, 用分号分隔一组, 等号分隔key 和value * @return 如果 config 是null,返回 emptyMap()
* 如果 config 是empty,返回 emptyMap()
* @since 3.3.4 * @apiNote 自动去除空格,忽略空 */ public static Map toSingleValueMap(String config){ return toSingleValueMap(config, String.class, String.class); } /** * 将 189=988;200=455;这种格式的字符串转换成map , map的key 是189这种, value 是988,455 这种等号后面的值,使用逗号分隔成list. *

示例:

* *
* *
     * 
     * Map map = StringUtil.toSingleValueMap("1=2;89=100;200=230", Integer.class, Integer.class);
     * 
     * assertThat(
     *                 map,
     *                 allOf(//
     *                                 hasEntry(1, 2),
     *                                 hasEntry(89, 100),
     *                                 hasEntry(200, 230)));
     * 
     * 
* * *
* * @param * the generic type * @param * the value type * @param config * 189=988;200=455; 这种格式的配置字符串, 用分号分隔一组, 等号分隔key 和value * @param keyClass * key转换的类型,比如上面的背景中, 189 可以转换成String Long Integer * @param valueElementClass * value转换的类型,比如上面的背景中, 455 可以转换成String Long Integer * @return 如果 config 是null,返回 emptyMap()
* 如果 config 是empty,返回 emptyMap()
* 如果 keyClass 是null,抛出 {@link NullPointerException}
* 如果 valueElementClass 是null,抛出 {@link NullPointerException}
* @since 3.3.4 * @apiNote 自动去除空格,忽略空 */ public static Map toSingleValueMap(String config,Class keyClass,Class valueElementClass){ if (isNullOrEmpty(config)){ return emptyMap(); } Validate.notNull(keyClass, "keyClass can't be null!"); Validate.notNull(valueElementClass, "valueElementClass can't be null!"); //--------------------------------------------------------------- String[] entrys = StringUtil.tokenizeToStringArray(config, ";"); if (isNullOrEmpty(entrys)){ return emptyMap(); } Map map = newHashMap(); for (String keyAndValueString : entrys){ if (isNullOrEmpty(keyAndValueString)){ continue; } String[] keyAndValues = StringUtil.tokenizeToStringArray(keyAndValueString, "="); if (size(keyAndValues) != 2){ continue; } //--------------------------------------------------------------- map.put( convert(keyAndValues[0], keyClass), // convert(keyAndValues[1], valueElementClass)); } return map; } /** * 将 189=988,900;200=455;这种格式的字符串转换成map , map的key 是189,200 这种, value 是988,900,455 这种等号后面的值,使用逗号分隔成list. *

示例:

* *
* *
     * 
     * Map> multiValueMap = StringUtil.toMultiValueMap("1=2;89=100,99;200=230,999", Integer.class, Integer.class);
     * 
     * assertThat(
     *                 multiValueMap,
     *                 allOf(//
     *                                 hasEntry(1, toList(2)),
     *                                 hasEntry(89, toList(100, 99)),
     *                                 hasEntry(200, toList(230, 999))));
     * 
     * 
* * *
* * @param * the generic type * @param * the value type * @param config * 189=988,900;200=455; 这种格式的配置字符串, 用分号分隔一组, 等号分隔key 和value * @param keyClass * key转换的类型,比如上面的背景中, 189 可以转换成String Long Integer * @param valueElementClass * value转换的类型,比如上面的背景中, 455 可以转换成String Long Integer * @return 如果 config 是null,返回 emptyMap()
* 如果 config 是empty,返回 emptyMap()
* 如果 keyClass 是null,抛出 {@link NullPointerException}
* 如果 valueElementClass 是null,抛出 {@link NullPointerException}
* @since 3.3.4 * @apiNote 自动去除空格,忽略空 */ public static Map> toMultiValueMap(String config,Class keyClass,Class valueElementClass){ if (isNullOrEmpty(config)){ return emptyMap(); } Validate.notNull(keyClass, "keyClass can't be null!"); Validate.notNull(valueElementClass, "valueElementClass can't be null!"); //--------------------------------------------------------------- String[] entrys = tokenizeToStringArray(config, ";"); if (isNullOrEmpty(entrys)){ return emptyMap(); } //--------------------------------------------------------------- Map> map = newHashMap(); for (String entry : entrys){ if (isNullOrEmpty(entry)){ continue; } String[] keyAndValuesMapping = tokenizeToStringArray(entry, "="); if (size(keyAndValuesMapping) != 2){ continue; } //--------------------------------------------------------------- T key = convert(keyAndValuesMapping[0], keyClass); String[] values = StringUtil.tokenizeToStringArray(keyAndValuesMapping[1], ","); List valueList = toList(toArray(values, valueElementClass)); map.put(key, valueList); } return map; } // [start]format /** * 将各类数据,使用{@link String#format(String, Object...)}格式化为字符串. * *

规则:

*
* *
    * *
  1. * *

    * 对整数进行格式化: *

    * *

    * 由4部分组成:%[index$][标识][最小宽度]转换方式 *

    * *

    * 此外,StringUtil.format("%03d", 1) 不能写成 StringUtil.format("%03d", "1") *

    *
  2. * *
  3. *

    * 对浮点数进行格式化: *

    * *

    * %[index$][标识][最少宽度][.精度]转换方式 *

    * *
  4. * *
  5. %index$开头,index从1开始取值,表示将第index个参数拿进来进行格式化.
  6. * *
* *
* *

* 转换符和标志的说明: *

* *

转换符

* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
转换符说明示例
%s字符串类型"mingrisoft"
%c字符类型'm'
%b布尔类型true
%d整数类型(十进制)99
%x整数类型(十六进制)FF
%o整数类型(八进制)77
%f浮点类型99.99
%a十六进制浮点类型FF.35AE
%e指数类型9.38e+5
%g通用浮点类型(f和e类型中较短的)
%h散列码
%%百分比类型
%n换行符
%tx日期与时间类型(x代表不同的日期与时间转换符
*
* *

标志

* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
标志说明示例结果
+为正数或者负数添加符号("%+d",15)+15
-左对齐(不可以与"用0填充"同时使用)("%-5d",15)|15 |
0数字前面补0("%04d", 99)0099
空格在整数之前添加指定数量的空格("% 4d", 99)| 99|
,以","对数字分组("%,f", 9999.99)9,999.990000
(使用括号包含负数("%(f", -99.99)(99.990000)
#如果是浮点数则包含小数点,如果是16进制或8进制则添加0x或0("%#x", 99)
* ("%#o", 99)
0x63
* 0143
{@code <}格式化前一个转换符所描述的参数("%f和%{@code <}3.2f", 99.45)99.450000和99.45
$被格式化的参数索引("%1$d,%2$s", 99,"abc")99,abc
*
* * @param format * the format * @param args * the args * @return 如果 format 是null,返回 {@link StringUtils#EMPTY}
* 如果 format 包含不需要转化的字符串,这些字符串是你写什么,最终就输出什么
* 否则返回 {@link String#format(String, Object...)} * @see java.util.Formatter * @see String#format(String, Object...) * @see String#format(java.util.Locale, String, Object...) * @since JDK 1.5 */ public static String format(String format,Object...args){ return null == format ? EMPTY : String.format(format, args); } /** * 格式化字符串,此方法是抽取slf4j的核心方法. * *

* 在java中,常会拼接字符串生成新的字符串值,在字符串拼接过程中容易写错或者位置写错
*
* slf4j的log支持格式化输出log,比如:
*

* *
    *
  • LOGGER.debug("{}","feilong");
  • *
  • LOGGER.info("{},{}","feilong","hello");
  • *
* * 这些写法非常简洁且有效,不易出错 * *
* 因此,你可以在代码中出现这样的写法: * *
     * throw new IllegalArgumentException(StringUtil.formatPattern(
     *  "callbackUrl:[{}] ,length:[{}] can't {@code >}{}",
     *  callbackUrl,
     *  callbackUrlLength,
     *  callbackUrlMaxLength)
     * 
* * 又或者 * *
     * return StringUtil.formatPattern("{} [{}]", encode, encode.length());
     * 
* * @param messagePattern * message的格式,比如 callbackUrl:[{}] ,length:[{}] * @param args * 参数 * @return 如果 messagePattern 是null,返回 null
* 如果 args 是null,返回 messagePattern
* @see org.slf4j.helpers.FormattingTuple * @see org.slf4j.helpers.MessageFormatter#arrayFormat(String, Object[]) * @see org.slf4j.helpers.FormattingTuple#getMessage() * @since 3.3.8 */ public static String formatPattern(String messagePattern,Object...args){ if (null == messagePattern){ return EMPTY; } FormattingTuple formattingTuple = MessageFormatter.arrayFormat(messagePattern, args); return formattingTuple.getMessage(); } // [end] /** * 将两个字符串,去空格之后忽视大小写对比. * * *
     * StringUtil.trimAndEqualsIgnoreCase(null, null)   = true
     * StringUtil.trimAndEqualsIgnoreCase(null, "")     = false
     * StringUtil.trimAndEqualsIgnoreCase("", "  ")     = true
     * StringUtil.trimAndEqualsIgnoreCase("", null)     = false
     * StringUtil.trimAndEqualsIgnoreCase("feilong  ", "   feilong")     = true
     * 
* * @param s1 * 第1个字符串 * @param s2 * 第2个字符串 * @return 如果 s1, s2 都是null,返回 true
* @since 3.1.1 */ public static boolean trimAndEqualsIgnoreCase(String s1,String s2){ return StringUtils.equalsIgnoreCase(StringUtils.trim(s1), StringUtils.trim(s2)); } /** * 清除charSequence 中一些特殊符号, 目前会清除 backspace (\b), 三个特殊空格 \u00A0,\u2007,\u202F. * * @param charSequence * the char sequence * @return 如果 charSequence 是null,返回 {@link #EMPTY}
* 如果 charSequence 是empty,,返回 {@link #EMPTY}
* @since 3.3.8 */ public static String clean(CharSequence charSequence){ if (isNullOrEmpty(charSequence)){ return EMPTY; } //--------------------------------------------------------------- String string = removeSpecialSpace(charSequence); //去空格 string = StringUtils.trim(string); // 反斜杠(\)在字符串内有特殊含义,用来表示一些特殊字符,所以又称为转义符。 // \0:null(\u0000) // \b :后退键(\u0008) // \f :换页符(\u000C) // n :换行符(000A) // r :回车键(000D) // \t :制表符(\u0009) // \v :垂直制表符(\u000B) // \' :单引号(\u0027) // \" :双引号(\u0022) // \\ :反斜杠(\u005C) // '\251' // "©" // '\xA9' // "©" // '\u00A9' // "©" //后退键 backspace see https://github.com/ifeilong/feilong/issues/55 string = com.feilong.lib.lang3.StringUtils.remove(string, '\u0008'); return string; } /** * 删除特殊空格. * *

* \u00A0,\u2007,\u202F *

* * @param charSequence * the char sequence * @return the string * @since 3.3.8 */ public static String removeSpecialSpace(CharSequence charSequence){ String string = ConvertUtil.toString(charSequence); //不间断空格 as a space,but often not adjusted see https://github.com/ifeilong/feilong/issues/56 string = com.feilong.lib.lang3.StringUtils.remove(string, '\u00A0'); //字符U+2007---U+200A和U+202F在Unicode标准中没有给它们分配精确的宽度,字符的显示实现可能会与预期的宽度有很大偏差。 //此外,在出版软件中使用相同名称的概念时,比如“窄空格”,其含义可能会有很大的不同。 //例如, //在 InDesign 软件中,“thin space窄空格”是1/8 em (即0.125 em,与建议的0.2 em 相反) , //而“hair space发际空格”只有1/24 em (即大约0.042 em,而窄空格标志的宽度通常在0.1em和0.2 em之间变化)。 //figure space string = com.feilong.lib.lang3.StringUtils.remove(string, '\u2007'); //narrow no-break space //比不间断空格(或者空格)都窄 string = com.feilong.lib.lang3.StringUtils.remove(string, '\u202F'); return string; } }