String[] tokens = "abc".split("");
)时,分割机制将在标记有
|
的位置分割br />因为每个字符前后都有空白""
。因此,结果将首先生成此数组|a|b|c|
,随后将删除结尾的空字符串(因为我们没有为
limit
参数明确提供负值),因此最终将返回["", "a", "b", "c", ""]
在Java 8中,拆分机制似乎已更改。现在,当我们使用
["", "a", "b", "c"]
时,我们将获得
["a", "b", "c"]
数组而不是["", "a", "b", "c"]
,因此看起来开始时的空字符串也被删除了。但是该理论失败了,因为例如"abc".split("")
返回数组
["", "bc"]
处带有空字符串。 有人可以解释这里发生的事情以及Java 8中拆分规则的变化吗?
#1 楼
String.split
(称为Pattern.split
)的行为在Java 7和Java 8之间发生了变化。文档
比较Java 7和Java 8中的
Pattern.split
的文档,我们观察到添加以下子句:当输入序列的开头存在正宽度匹配时,则在结果数组的开头包含一个空的前导子字符串。但是开头的零宽度匹配永远不会产生这样的空前导子字符串。
与Java 7相比,Java 8中的
String.split
也添加了相同的子句。参考实现
让我们比较一下Java 7和Java 8中的参考实现的
Pattern.split
的代码。该代码是从grepcode中检索的,用于版本7u40-b43和8-b132。 /> Java 7
public String[] split(CharSequence input, int limit) {
int index = 0;
boolean matchLimited = limit > 0;
ArrayList<String> matchList = new ArrayList<>();
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);
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()};
// Add remaining segment
if (!matchLimited || matchList.size() < limit)
matchList.add(input.subSequence(index, input.length()).toString());
// Construct result
int resultSize = matchList.size();
if (limit == 0)
while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
resultSize--;
String[] result = new String[resultSize];
return matchList.subList(0, resultSize).toArray(result);
}
Java 8
public String[] split(CharSequence input, int limit) {
int index = 0;
boolean matchLimited = limit > 0;
ArrayList<String> matchList = new ArrayList<>();
Matcher m = matcher(input);
// Add segments before each match found
while(m.find()) {
if (!matchLimited || matchList.size() < limit - 1) {
if (index == 0 && index == m.start() && m.start() == m.end()) {
// no empty leading substring included for zero-width match
// at the beginning of the input char sequence.
continue;
}
String match = input.subSequence(index, m.start()).toString();
matchList.add(match);
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()};
// Add remaining segment
if (!matchLimited || matchList.size() < limit)
matchList.add(input.subSequence(index, input.length()).toString());
// Construct result
int resultSize = matchList.size();
if (limit == 0)
while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
resultSize--;
String[] result = new String[resultSize];
return matchList.subList(0, resultSize).toArray(result);
}
添加以下代码Java 8中的代码排除了输入字符串开头的零长度匹配,这解释了上面的行为。
if (index == 0 && index == m.start() && m.start() == m.end()) {
// no empty leading substring included for zero-width match
// at the beginning of the input char sequence.
continue;
}
维护兼容性
遵循Java 8及更高版本中的行为
要使
split
在各个版本中的行为保持一致并与Java 8中的行为兼容:长度的字符串,只需在正则表达式的末尾添加(?!\A)
并将原始正则表达式换成非capt小组(?:...)
(如有必要)。如果您的正则表达式不能匹配零长度的字符串,则无需执行任何操作。
如果您不知道正则表达式是否可以匹配零-长度字符串是否正确,请同时执行步骤1中的所有操作。字符串。
Java 7及更低版本中的以下行为
没有通用的解决方案可以使
(?!\A)
与Java 7及更低版本向后兼容,而不能替换split
的所有实例以指向您自己的自定义实现。评论
知道如何更改split(“”)代码,使其在不同的Java版本之间保持一致吗?
–丹尼尔(Daniel)
2015年10月29日在22:22
@Daniel:通过在正则表达式的末尾添加(?!^)并将原始正则表达式包装在非捕获组(?:...)中,可以使其向前兼容(遵循Java 8的行为)( (如有必要),但我想不出任何办法使其向后兼容(遵循Java 7和以前的旧行为)。
– nhahtdh
15-10-30在3:50
感谢您的解释。您可以描述“(?!^)”吗?在什么情况下它将与“”不同? (我在regex上很糟糕!:-/)。
–丹尼尔(Daniel)
2015年10月31日,下午3:44
@Daniel:它的含义受Pattern.MULTILINE标志影响,而\ A始终匹配字符串的开头而不考虑标志。
– nhahtdh
2015年11月2日,下午2:38
#2 楼
这已在split(String regex, limit)
的文档中指定。如果此字符串的开头存在正宽度匹配
,则开头将包含一个空的前导子字符串
结果数组的值。开头为零宽度的匹配项,但是永远不会
产生这样的空前导子串。不包含在结果数组中。
然而,当在
"abc".split("")
上进行拆分时,在第二个片段中,您获得了正宽度匹配(在这种情况下为1),因此按预期包括了空的前导子字符串。删除了无关的源代码)评论
这只是一个问题。可以从JDK中发布一段代码吗?还记得Google-Harry Potter-Oracle的版权问题吗?
– Paul Vargas
2014年3月28日在17:24
@PaulVargas坦白地说,我不知道,但我认为可以,因为您可以下载JDK,并解压缩包含所有源代码的src文件。因此,从技术上讲,每个人都可以看到源。
– Alexis C.
2014年3月28日在17:28
@PaulVargas“开源”中的“开放”确实代表着某种东西。
– Marko Topolnik
2014年3月28日在17:49
@ZouZou:仅仅因为每个人都可以看到它并不意味着您可以重新发布它
–user102008
2014年5月14日20:20
@Paul Vargas,IANAL,但在许多其他情况下,此类帖子属于引用/合理使用情况。有关此主题的更多信息,请访问:meta.stackexchange.com/questions/12527/…
– Alex Pakka
2014年5月16日下午5:16
#3 楼
从Java 7到Java 8,split()
的文档进行了细微更改,具体来说,添加了以下语句:当此开头有一个正宽匹配时字符串,然后在结果数组的开头包含一个空的前导子字符串。但是开头的零宽度匹配永远不会产生这样的空前导子字符串。
(强调我的)
空字符串拆分会生成零宽度匹配开头,因此根据上面指定的内容,结果数组的开头将不包含空字符串。相比之下,在
"a"
上分割的第二个示例在字符串的开头生成一个正宽度匹配,因此实际上在结果数组的开头包含一个空字符串。评论
再过几秒钟就变得与众不同。
– Paul Vargas
2014年3月28日在17:11
@PaulVargas实际上在这里arshajii在走走几秒钟之前就发布了答案,但不幸的是走走在这里早些时候回答了我的问题。我想知道是否应该问这个问题,因为我已经知道了一个答案,但是这似乎很有趣,邹邹的早期评论也应得到一定的声誉。
– Pshemo
2014年3月28日在17:13
尽管新行为看起来更合乎逻辑,但是显然这是向后兼容性中断。进行此更改的唯一理由是“ some-string” .split(“”)是非常罕见的情况。
–ivstas
14-10-29在6:45
.split(“”)不是不匹配任何内容的唯一分割方法。我们在jdk7中使用了正向前瞻正则表达式,该正则表达式也在开始时就匹配了,并产生了一个空的head元素,该元素现在消失了。 github.com/spray/spray/commit/…
– jrudolph
15年2月10日在10:59
评论
Java8似乎可以解决该问题。同时,s.split(“(?!^)”)似乎可以工作。@shkschneider我的问题中描述的行为不是Java-8之前版本的错误。这种行为不是特别有用,但是仍然是正确的(如我的问题所示),因此我们不能说它是“固定的”。我认为它更像是一种改进,因此我们可以使用split(“”)而不是神秘的(对于不使用正则表达式的人来说)split(“(?!^)”)或split(“(?<!^)”)或其他少数正则表达式。
将fedora升级到Fedora 21后,遇到同样的问题,fedora 21随JDK 1.8一起提供,因此我的IRC游戏应用程序已损坏。
这个问题似乎是Java 8中这一重大更改的唯一文档。Oracle将其排除在不兼容列表之外。
JDK中的这一更改仅花费了我2个小时来找出问题所在。该代码在我的计算机(JDK8)上运行良好,但在另一台计算机(JDK7)上却神秘地失败了。 Oracle确实应该更新String.split(String regex)的文档,而不是Pattern.split或String.split(String regex,int limit),因为这是迄今为止最常见的用法。 Java因其可移植性(又称为WORA)而闻名。这是一个重大的重大突破,根本没有得到很好的记录。