背景
public static void main(String[] args) {
String netmask = "103.140.147.0/24";
System.out.println(org.springframework.util.StringUtils.split(netmask, ","));
System.out.println(org.apache.commons.lang3.StringUtils.split(netmask, ","));
}
有这么一段常见的代码,可以想象结果吗? 今天在项目测试中就出现了 误用StringUtils.split. 方法导致的 NullPointException。
根据 异常 NullPointException 提示,我定位了到 StringUtils.split 方法返回null,但不符合 逻辑啊, 不满足 分片,应该直接返回 本身 才对。况且以前也在用的,点进去查看了 jar,并看了方法。
发现引用了 org.springframework.util.StringUtils
/**
* Split a {@code String} at the first occurrence of the delimiter.
* Does not include the delimiter in the result.
* @param toSplit the string to split
* @param delimiter to split the string up with
* @return a two element array with index 0 being before the delimiter, and
* index 1 being after the delimiter (neither element includes the delimiter);
* or {@code null} if the delimiter wasn't found in the given input {@code String}
*/
@Nullable
public static String[] split(@Nullable String toSplit, @Nullable String delimiter) {
if (!hasLength(toSplit) || !hasLength(delimiter)) {
return null;
}
int offset = toSplit.indexOf(delimiter);
if (offset < 0) {
return null;
}
String beforeDelimiter = toSplit.substring(0, offset);
String afterDelimiter = toSplit.substring(offset + delimiter.length());
return new String[] {beforeDelimiter, afterDelimiter};
}
其实也解释的很清楚了,分隔符 不存在,就直接返回了 null
分析
在改成 org.apache.commons.lang3.StringUtils, 才是符合我们要求的
public static String[] split(final String str, final String separatorChars) {
return splitWorker(str, separatorChars, -1, false);
}
/**
* Performs the logic for the {@code split} and
* {@code splitPreserveAllTokens} methods that return a maximum array
* length.
*
* @param str the String to parse, may be {@code null}
* @param separatorChars the separate character
* @param max the maximum number of elements to include in the
* array. A zero or negative value implies no limit.
* @param preserveAllTokens if {@code true}, adjacent separators are
* treated as empty token separators; if {@code false}, adjacent
* separators are treated as one separator.
* @return an array of parsed Strings, {@code null} if null String input
*/
private static String[] splitWorker(final String str, final String separatorChars, final int max, final boolean preserveAllTokens) {
// Performance tuned for 2.0 (JDK1.4)
// Direct code is quicker than StringTokenizer.
// Also, StringTokenizer uses isSpace() not isWhitespace()
if (str == null) {
return null;
}
final int len = str.length();
if (len == 0) {
return ArrayUtils.EMPTY_STRING_ARRAY;
}
final List<String> list = new ArrayList<>();
int sizePlus1 = 1;
int i = 0, start = 0;
boolean match = false;
boolean lastMatch = false;
if (separatorChars == null) {
// Null separator means use whitespace
while (i < len) {
if (Character.isWhitespace(str.charAt(i))) {
if (match || preserveAllTokens) {
lastMatch = true;
if (sizePlus1++ == max) {
i = len;
lastMatch = false;
}
list.add(str.substring(start, i));
match = false;
}
start = ++i;
continue;
}
lastMatch = false;
match = true;
i++;
}
} else if (separatorChars.length() == 1) {//我的场景
// Optimise 1 character case
final char sep = separatorChars.charAt(0);
while (i < len) {
if (str.charAt(i) == sep) {
if (match || preserveAllTokens) {
lastMatch = true;
if (sizePlus1++ == max) {
i = len;
lastMatch = false;
}
list.add(str.substring(start, i)); //这里 分割
match = false;
}
start = ++i;
continue;
}
lastMatch = false;
match = true;
i++;
}
} else {
// standard case 这里像是有满足 分隔符中的字符 就进行分割
while (i < len) {
if (separatorChars.indexOf(str.charAt(i)) >= 0) {
if (match || preserveAllTokens) {
lastMatch = true;
if (sizePlus1++ == max) {
i = len;
lastMatch = false;
}
list.add(str.substring(start, i));
match = false;
}
start = ++i;
continue;
}
lastMatch = false;
match = true;
i++;
}
}
if (match || preserveAllTokens && lastMatch) {
list.add(str.substring(start, i)); //遍历完自符,如果分隔符不存在,则返回 str 本身
}
return list.toArray(new String[list.size()]);
}
扩展
org.apache.commons.lang3.StringUtils@split 方法能满足一般的情况,但是 他不会 对 空格做处理,如果有特殊要求,可以用 guava的 工具类
List<String> hostNameList = Splitter.on(",")
.trimResults()
.omitEmptyStrings()//可以 选择是否对 空字符串 做处理
.splitToList(hostName);
guava 是一种很强大的工具,能想到的功能基本都有,大家有空可以多看看它里面的实现和 设计思路。