split()函数的基本功能
根据给定正则表达式的匹配拆分此字符串。
先观察一些现象
请先阅读代码,并预测代码的结果。
第一段代码
public static void main(String[] args) {
String s = "aba";
String[] strings = s.split("b");
System.out.println("strings.length = " + strings.length);
for (int i = 0; i < strings.length; i++) {
String string = strings[i];
System.out.println(i + "(" + string.length() + "): " + string);
}
}
结果
strings.length = 2
0(1): a
1(1): a
结果共分为三个部分:括号前的数字表示序号;括号内的数字表示对应字符串的长度;冒号后即为对应的字符串。
结果符合我们对于split()函数的认知。
第二段代码
public static void main(String[] args) {
String s = "abba";
String[] strings = s.split("b");
System.out.println("strings.length = " + strings.length);
for (int i = 0; i < strings.length; i++) {
String string = strings[i];
System.out.println(i + "(" + string.length() + "): " + string);
}
}
结果
strings.length = 3
0(1): a
1(0):
2(1): a
这里我们观察到,由于s
中存在两个连着的字符b,因此split()
在分割时会产生一个空字符串。
第三段代码
public static void main(String[] args) {
String s = "ba";
String[] strings = s.split("b");
System.out.println("strings.length = " + strings.length);
for (int i = 0; i < strings.length; i++) {
String string = strings[i];
System.out.println(i + "(" + string.length() + "): " + string);
}
}
结果
strings.length = 2
0(0):
1(1): a
这里我们观察到,由于s
中的字符b处于开头位置,因此split()
在分割时会在第一个结果处放入一个空字符串。
第四段代码
public static void main(String[] args) {
String s = "bba";
String[] strings = s.split("b");
System.out.println("strings.length = " + strings.length);
for (int i = 0; i < strings.length; i++) {
String string = strings[i];
System.out.println(i + "(" + string.length() + "): " + string);
}
}
结果
strings.length = 3
0(0):
1(0):
2(1): a
这里我们观察到,结果中出现了两个空字符串,通过前面的分析,不难得出,第一个空字符串来自于s
的第一个字符b,第二个空字符串来自于s
中两个连续的字符b。
第五段代码
public static void main(String[] args) {
String s = "ab";
String[] strings = s.split("b");
System.out.println("strings.length = " + strings.length);
for (int i = 0; i < strings.length; i++) {
String string = strings[i];
System.out.println(i + "(" + string.length() + "): " + string);
}
}
结果
strings.length = 1
0(1): a
这里我们观察到,结果中仅有一个字符,这说明如果字符b处在s
的末尾,则不会产生新的空字符串。
第六段代码
public static void main(String[] args) {
String s = "abb";
String[] strings = s.split("b");
System.out.println("strings.length = " + strings.length);
for (int i = 0; i < strings.length; i++) {
String string = strings[i];
System.out.println(i + "(" + string.length() + "): " + string);
}
}
根据前面的经验,两个连续的字符b会产生一个空字符串,结尾的b不会产生空字符串,但是结果并非如此。
结果
strings.length = 1
0(1): a
该结果与第五段代码的运行结果一致,两个连续的字符b并没有产生空字符串,说明相同字符串在结尾囤积并不会产生空字符串。为验证这一猜测,可以继续研究。
第七段代码
public static void main(String[] args) {
String s = "babbabbb";
String[] strings = s.split("b");
System.out.println("strings.length = " + strings.length);
for (int i = 0; i < strings.length; i++) {
String string = strings[i];
System.out.println(i + "(" + string.length() + "): " + string);
}
}
结果
strings.length = 4
0(0):
1(1): a
2(0):
3(1): a
这里我们观察到,结果中出现了两个空字符串,第一个空字符串来自于s
的第一个字符b,第二个空字符串来自于s
中两个连续的字符b。而结尾的字符b并没有产生更多的空字符串。基本验证这一假设。
第八段代码
public static void main(String[] args) {
String s = "bbb";
String[] strings = s.split("b");
System.out.println("strings.length = " + strings.length);
for (int i = 0; i < strings.length; i++) {
String string = strings[i];
System.out.println(i + "(" + string.length() + "): " + string);
}
}
结果
strings.length = 0
而执行结果告诉我们,如果s
中的所有字符都被匹配时,split()
会返回一个空数组。
源码分析
通过源码分析,我们可以更加深入的理解split()
对于空字符串的处理。
public String[] split(String regex, int limit) {
/* fastpath if the regex is a
(1)one-char String and this character is not one of the
RegEx's meta characters ".$|()[{^?*+\\", or
(2)two-char String and the first char is the backslash and
the second is not the ascii digit or ascii letter.
*/
char ch = 0;
if (((regex.length() == 1 &&
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
(regex.length() == 2 &&
regex.charAt(0) == '\\' &&
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
((ch-'a')|('z'-ch)) < 0 &&
((ch-'A')|('Z'-ch)) < 0)) &&
(ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE))
{
int off = 0;
int next = 0;
boolean limited = limit > 0;
ArrayList<String> list = new ArrayList<>();
while ((next = indexOf(ch, off)) != -1) {
if (!limited || list.size() < limit - 1) {
list.add(substring(off, next));
off = next + 1;
} else { // last one
//assert (list.size() == limit - 1);
int last = length();
list.add(substring(off, last));
off = last;
break;
}
}
// If no match was found, return this
if (off == 0)
return new String[]{this};
// Add remaining segment
if (!limited || list.size() < limit)
list.add(substring(off, length()));
// Construct result
int resultSize = list.size();
if (limit == 0) {
while (resultSize > 0 && list.get(resultSize - 1).isEmpty()) {
resultSize--;
}
}
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);
}
return Pattern.compile(regex).split(this, limit);
}
我们关心的部分在于
if (limit == 0) {
while (resultSize > 0 && list.get(resultSize - 1).isEmpty()) {
resultSize--;
}
}
其逻辑很明晰:在参数limit
为0时,它会删除位于结尾的空字符串,而不传limit
时,其数值默认为0。
public String[] split(String regex) {
return split(regex, 0);
}
至此,逻辑被成功厘清。
阅读文档
实际上,在源码中关于split()
函数的描述中又这样一句话:
Trailing empty strings are therefore not included in the resulting array.
大致的翻译为“因此,所得数组中不包括结尾空字符串”。
我们也可以在jdk的中文文档中找到这句话。