Regex matching unescaped commas in Java
问题描述
我正试图使用String类提供的split()方法将a拆分为单独的字符串。文档告诉我它将围绕参数的匹配项进行拆分,这是一个正则表达式。我使用的分隔符是逗号,但逗号也可以转义。我使用的逃逸字符是一个前斜杠/(只是为了不使用反斜杠来使事情变得更容易,因为这需要在Java和正则表达式中的字符串文本中附加的逃逸)。
例如,输入可能是:
输出应为:
号
所以,字符串应该在每个逗号处拆分,除非逗号前面有奇数个斜杠(1,3,5,7,…,∞),因为这意味着逗号被转义了。
可能的解决方案
我最初的猜测是这样拆分它:
1
| String[] strings = longString. split("(?<![^/](//)*/),"); |
但这是不允许的,因为Java不允许在组后面进行无限的查看。我可以通过用02000替换*将复发率限制在2000年:
1
| String[] strings = longString. split("(?<![^/](//){0,2000}/),"); |
。
但这仍然对输入施加了限制。所以我决定从"幕后观察"组中剔除这种复发,并得出了以下结论:
1
| String[] strings = longString. split("(?<!/)(?:(//)*),"); |
但是,它的输出是以下字符串列表:
1 2 3 4
| a
b,b (the final slash is lacking in the output)
c/, (the final slash is lacking in the output)
d/, |
。
为什么那些斜线在第二和第三弦中被省略,我怎样才能解决它(在爪哇)?
您可以使用逗号前面的偶数个斜杠的正查找来实现拆分:
1
| String[] strings = longString. split("(?<=[^/](//){0,999999999}),"); |
但要显示所需的输出,需要进一步删除其余转义:
1 2 3 4
| String longString ="a,b/,b//,c///,//,d///,";
String[] strings = longString. split("(?<=[^/](//){0,999999999}),");
for (String s : strings )
System. out. println(s. replaceAll("/(.)", "$1")); |
。
输出:
- 不幸的是,如果输入有连续的20个斜杠,这将中断,不是吗?我已经提到我可以通过将重复次数限制在2000来解决这个问题,但是这仍然会对输入造成限制,即使2000年通常足够了。
- 我已经改变了regex,以满足高达10亿的削减。够了吗?
- 我看到你编辑了你的答案。{ 0999999999 }实际上是无限的,但是我不确定即使Java编译器不抱怨它,它也能保证工作。它真的能在9999999 8个连续斜杠的序列上工作吗?:)我同意这种类型的解决方案是绝对可用的,但我希望找到一种不限制这种重复发生的解决方案。在Java中是不可能的,还是很难?
- 是的,它会起作用的。任何量词都有2147483647的执行限制(见Integer.MAX_VALUE)。afaik,此限制适用于所有regex实现。
你离得很近。要克服查找错误,可以使用以下解决方法:
1
| String[] strings = longString. split("(?<![^/](//){0,99}/),") |
号
- 就像波西米亚人的回答一样,如果输入连续有200个斜线,那么这个问题就会被打破。我正在寻找一种不限制连续斜杠数量的方法。
- 99作为一种工作已经被赋予,你可以使它成为一个庞大的数字来涵盖所有的实际可能性。在断言后面不可能有可变长度的LoCK,而这个限制是用Java中的正则表达式引擎实现的。此外,我很惊讶您在最初的问题中选择忽略了这个错误。
如果您不介意使用regex的其他方法,我建议使用.matcher:
1 2 3 4 5 6
| Pattern pattern = Pattern. compile("(?:[^,/]+|/.)+");
String test ="a,b/,b//,c///,//,d///,";
Matcher matcher = pattern. matcher(test );
while (matcher. find()) {
System. out. println(matcher. group(). replaceAll("/(.)", "$1"));
} |
。
输出:
Ideone演示
此方法将匹配除分隔逗号(有点相反)之外的所有内容。其优点是它不依赖于观察。
- 谢谢。这是一个很好的解决方案。但是,我不清楚如何使用非捕获组。在这种情况下需要这样做吗?
- @富兰克林一组是绝对需要的,这样组[^,/]+|/.就可以通过+重复。好吧,您可以使用([^,/]+|/.),但这将在变量中存储一些内容,因此需要更多的内存。它没什么作用,但我更喜欢尽可能避免捕获组。如果你有很多事情要做,他们往往会放慢速度。
- 再次感谢杰瑞。听起来很有道理。:-)关于这个组的另一个问题是:我们不能去掉[^,/]部分的内部重复吗?(我已经很久没有玩过regex了…)我接受你的答案,因为它允许"无限"(当然不是无限)的转义字符重复次数。你能告诉我这是否比string.split()更快/更慢吗?
- @富兰克林,嗯,恐怕我不知道你去除内心的重复意味着什么。如果我把它弄好了,你可以把它改短一点,我猜是:(?:/.|[^,])+。
- @富兰克林,哦,对不起。我不知道时间函数在C中是如何工作的(不管怎样,现在还不知道),但值得一提的是,lookbehinds通常比较慢。
- 我的意思是:我们不能用(?:[^,/]|/.)+代替(?:[^,/]+|/.)+吗?还是有必要这样做的原因?
- @富兰克林哦!我相信里面的+有点快。regex默认情况下是贪婪的,因此在尝试重复(?:[^,/]|/.)之前,您可以先尽可能多地重复[^,/]。这意味着您尽可能长时间地呆在内部循环中,只有在满足特殊字符(转义和非法的,时才"中断"。再说一次,我没有任何数据来证明它的速度更快,但我认为这两者之间的差异不应该是显著的。
我喜欢regex,但是在这里手动编写代码不是很容易吗,也就是说。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| boolean escaped = false;
for(int i = 0, len = s.length() ; i < len ; i++){
switch(s.charAt(i)){
case"/": escaped = !escaped; break;
case",":
if(!escaped){
//found a segment, do something with it
}
//Fallthrough!
default:
escaped = false;
}
}
// handle last segment |
- 实际上,我已经"手动"完成了这项工作,但我现在特别想用regex寻找一个解决方案。