通过深入研究源代码,我得到了这种行为背后的确切问题。
The String.split()
方法内部使用Pattern.split()
。 split 方法在返回结果数组之前检查最后一个匹配的索引或是否确实存在匹配。如果最后一个匹配的索引是0
,这意味着您的模式仅匹配字符串开头的空字符串或根本不匹配,在这种情况下,返回的数组是包含相同元素的单元素数组。
这是源代码:
public String[] split(CharSequence input, int limit) {
int index = 0;
boolean matchLimited = limit > 0;
ArrayList<String> matchList = new ArrayList<String>();
Matcher m = matcher(input);
// Add segments before each match found
while(m.find()) {
if (!matchLimited || matchList.size() < limit - 1) {
String match = input.subSequence(index, m.start()).toString();
matchList.add(match);
// Consider this assignment. For a single empty string match
// m.end() will be 0, and hence index will also be 0
index = m.end();
} else if (matchList.size() == limit - 1) { // last one
String match = input.subSequence(index,
input.length()).toString();
matchList.add(match);
index = m.end();
}
}
// If no match was found, return this
if (index == 0)
return new String[] {input.toString()};
// Rest of them is not required
如果上面代码中的最后一个条件 -index == 0
,为 true,则单个元素数组与输入字符串一起返回。
现在,考虑以下情况:index
can be 0
.
- 当根本没有匹配的时候。 (正如上面的评论中已经提到的那样)
-
如果在开头找到匹配项,则匹配字符串的长度为0
,那么索引中的值if
块(在while
环形) -
index = m.end();
将为 0。唯一可能的匹配字符串是空字符串(长度=0)。这里的情况正是如此。而且也不应该有任何进一步的比赛,否则index
将更新为不同的索引。
因此,考虑到您的情况:
For d%
,在第一个之前只有一个模式匹配d
。因此索引值将是0
。但由于没有任何进一步的匹配,索引值不会更新,并且if
条件变为true
,并返回带有原始字符串的单元素数组。
For d20+2
将会有两场比赛,一场之前d
,以及之前的一个+
。因此索引值将被更新,因此ArrayList
上面代码中的 将会被返回,其中包含由于分隔符(字符串的第一个字符)分割而导致的空字符串,正如 @Stema 的答案中已经解释的那样。
因此,为了获得您想要的行为(仅当分隔符不在开头时才在分隔符上拆分,您可以在正则表达式模式中添加负后视):
"(?<!^)(?=[dk+-])" // You don't need to escape + and hyphen(when at the end)
这将在空字符串上分割,后跟您的字符类,但前面不包含字符串的开头。
考虑分割字符串的情况"ad%"
在正则表达式模式上 -"a(?=[dk+-])"
。这将为您提供一个第一个元素为空字符串的数组。这里唯一的变化是,空字符串被替换为a
:
"ad%".split("a(?=[dk+-])"); // Prints - `[, d%]`
为什么?这是因为匹配字符串的长度是1
。所以第一次匹配后的索引值 -m.end()
不会是0
but 1
,因此不会返回单元素数组。