在 Java 中处理字符串时,split
是一个很常用的操作,但是这一简单的操作,却经常有意想不到的结果,就拿Guava库官方教程中的一个例子来说,",a,,b,".split(",")
的结果是?
1. "", "a", "", "b", ""
2. null, "a", null, "b", null
3. "a", null, "b"
4. "a", "b"
5. None of the above
正确答案应该是 5,以上都不对;正确结果是 ["", "a", "", "b"]
。
正是因为 JDK 自带的 split
这种奇怪的现象,其他开源库也都给出了自己的 split
方法,如 Apache Commons Lang 和上文中的 Guava 。
split in JDK8
String
类包含两个 split
重载方法,public String[] split(String regex)
和 public String[] split(String regex, int limit)
,调用前者就相当于默认 limit = 0
,而上面的例子中奇怪的现象就和这个 limit
有关。
JDK 文档中是这么解释的:
当
limit
即n
大于 0 时,会返回至多n
项,最后一项会包含所有未被拆分的部分当
n
小于 0 时,会返回所有拆分后的结果当
n
等于 0 时,会返回所有拆分后的结果,但是最后跟着的空字符串会被删除
由于使用了单参数的 split
方法,n == 0
,于是就产生了如上的结果。关于这一部分的 JDK 中的源码部分如下:
// Construct result
int resultSize = list.size();
if (limit == 0) {
while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
resultSize--;
}
}
平常在分析一些具有固定格式的数据时,比如每一行都是 tab
分割的,且有固定列数,那么进行解析时可以使用 s.split("\t", -1)
来进行操作。这样会保存所有的分割项,包含任意部位的空字符串,比如
":a::b::".split(":", -1) => ["", "a", "", "b", "", ""]
另外一个需要注意的地方是,split
接收的参数是一个正则表达式,这一点经常容易忽略。比如 "a.b.c".split(".")
的结果是 []
,长度为 0 ,因为首先 .
匹配任意字符,所以原字符串中每一个都是分割符,这就产生了 6 个空字符串, 然后 limit
默认为 0 ,从后往前删除空字符串,结果就为空。
split in Commons Lang
JDK 中的方法毕竟还是简单了一些,不能满足我们一些特殊需求,或者说不想使用正则,那么可以使用 Commons Lang 库中的方法。这些 split
方法有以下特点:
如果没有指定结果个数,都默认输出最多项
如果没有
PreserveAllTokens
后缀,默认将多个连续分割符视为 1 个,不保留任意位置空字符串
比如:
StringUtils.split("::a::b::", ":") => ["a", "b"]
需要注意的是 split(String str, String separatorChars)
方法中第二个参数的意义是每一个字符都被当成分割符,比如:
StringUtils.split(":a:b:", "ab") => [":", ":", ":"]
那么假如我想用 "ab"
整体作为分割符呢,可以使用 splitByWholeSeparator
方法:
StringUtils.splitByWholeSeparator("abcabc","ab") => ["c", "c"]
但这个方法有一个和其他方法表现不一致的地方,它保留了末尾的空字符串,且只保留一个。
StringUtils.splitByWholeSeparator("abb", "bb") => ["a", ""]
StringUtils.splitByWholeSeparator("bba", "bb") => ["a"]
StringUtils.splitByWholeSeparator("abbbbabbbb", "bb") =>["a", "a", ""]
另外一个我觉得很有用的就是一系列 splitPreserveAllTokens
重载函数了,因为默认输出所有结果,且保留了空字符串。和 JDK 中的 limit = -1
结果一致,但更易读一些。
split in Guava
假如你已经被上面这些特殊情况都绕晕了,不妨试试 Guava 库,它没有提供简单的一系列重载 split
方法,而是提供了一系列的工厂方法,采用链式调用,从而从方法名上就能看出结果,不用苦思冥想到底有没有陷阱。
Splitter.on(",")
.trimResults(CharMatcher.is(','))
.omitEmptyStrings()
.limit(2)
.split("a,,,,,b,,,,c,,,")
=> ["a", "b,,,,c"]
除了按照分割符外,还可以按照长度:
Splitter.fixedLength(3).split("abcde") => ["abc", "de"]
不像 JDK 和 Commons Lang 中的返回数组,Guava 返回 Iterable
和 List
,而且这个 Iterable
已经重载了 toString
,可以方便地进行打印测试。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。