com.feilong.core.lang.StringUtil Maven / Gradle / Ivy
Show all versions of feilong Show documentation
/*
* 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)} 的区别)
*
*
*
* 注意:
*
*
* - 该替换从字符串的开头朝末尾执行,例如,用 "b" 替换字符串 "aaa" 中的 "aa" 将生成 "ba" 而不是 "ab".
* - 虽然底层调用了{@link java.util.regex.Matcher#replaceAll(String) Matcher.replaceAll()},但是使用了
* {@link java.util.regex.Matcher#quoteReplacement(String) Matcher.quoteReplacement()} 处理了特殊字符
*
*
*
* @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
的子字符串.
*
* 说明:
*
*
* -
* 此方法底层调用的是 {@link java.util.regex.Matcher#replaceAll(String)},相同于
*
Pattern.compile(regex).matcher(str).replaceAll(repl)
*
*
*
*
* 示例:
*
*
*
*
* 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 如果 templateString
是 StringUtils.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
*
* @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...)}格式化为字符串.
*
* 规则:
*
*
*
*
* -
*
*
* 对整数进行格式化:
*
*
*
* 由4部分组成:%[index$][标识][最小宽度]转换方式
*
*
*
* 此外,StringUtil.format("%03d", 1) 不能写成 StringUtil.format("%03d", "1")
*
*
*
* -
*
* 对浮点数进行格式化:
*
*
*
* %[index$][标识][最少宽度][.精度]转换方式
*
*
*
*
* - %index$开头,index从1开始取值,表示将第index个参数拿进来进行格式化.
*
*
*
*
*
*
* 转换符和标志的说明:
*
*
* 转换符
*
*
*
*
* 转换符
* 说明
* 示例
*
*
*
* %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;
}
}