Java中正则表达式匹配未转义逗号

3

问题描述

我正在尝试使用String类提供的split()方法将a字符串拆分为单独的字符串。文档告诉我它会在匹配参数(即正则表达式)周围进行拆分。我使用的分隔符是逗号,但逗号也可以被转义。我使用的转义字符是斜杠 /(只是为了让事情变得更简单,不使用反斜杠,因为这需要在Java和正则表达式中的字符串字面量中进行额外的转义)。

例如,输入可能是这样的:

a,b/,b//,c///,//,d///,

输出结果应该是:

a
b,b/
c/,/
d/,

所以,应该在每个逗号处拆分字符串,除非该逗号前面有奇数个斜杠(1、3、5、7、...,∞),因为这意味着逗号被转义了。
可能的解决方案:
我的初步猜测是这样拆分的:
String[] strings = longString.split("(?<![^/](//)*/),");

但这是不允许的,因为Java不允许无限回溯组。我可以通过将*替换为{0,2000}来限制重复次数,例如:

String[] strings = longString.split("(?<![^/](//){0,2000}/),");

但是这仍然对输入有限制。因此,我决定将循环从反向引用组中去除,并得出了以下代码:

String[] strings = longString.split("(?<!/)(?:(//)*),");

然而,它的输出是以下字符串列表:
a
b,b (the final slash is lacking in the output)
c/, (the final slash is lacking in the output)
d/,

为什么第二个和第三个字符串中省略了斜杠,我该如何解决它(在Java中)?
4个回答

3

您已经非常接近了。为了克服回顾错误,您可以使用以下解决方法:

String[] strings = longString.split("(?<![^/](//){0,99}/),")

与Bohemian的答案一样,如果输入有200个连续的斜杠,那么这种方法会出错。我正在寻找一种不限制任何连续斜杠数量的方法。 - Franklin
作为一种解决方法,“99”已经被提供,您可以将其设置为一个大数以涵盖所有实际可能性。在Java的正则表达式引擎中,您不能有可变长度的后行断言限制。此外,我很惊讶您选择忽略了您原始问题中的这个错误。 - anubhava
1
建议使用非捕获组,例如 (?<![^/](?://){0,99}/)。像这个解决方案一样,干净简单,尽管在 99 限制的情况下理论上并不涵盖所有情况。 - YoYo

3
您可以使用正向预查来实现拆分,以逗号前面的 偶数 个斜杠为例:
String[] strings = longString.split("(?<=[^/](//){0,999999999}),");

但是为了显示您想要的输出,您需要进一步去除剩余的转义符:

String longString = "a,b/,b//,c///,//,d///,";
String[] strings = longString.split("(?<=[^/](//){0,999999999}),");
for (String s : strings)
    System.out.println(s.replaceAll("/(.)", "$1"));

输出:

a
b,b/
c/,/
d/,

不幸的是,如果输入有20个连续的斜杠,那么这将会出错,对吧?我已经提到过,我可以通过限制重复次数为2000来解决问题,但即使2000通常足够,这仍然会对输入产生限制。 - Franklin
我已经更改了正则表达式,以适应多达10亿个斜杠。这足够吗? - Bohemian
我看到你编辑了你的回答。{0,999999999} 实际上是无限,但我不确定它是否保证能够工作,尽管Java编译器没有投诉。它是否真的可以用于999999998个连续斜杠的序列? :)我同意这种解决方法肯定是可行的,但我希望找到一个不会限制这种重复的解决方案。在Java中是不可能实现的,还是只是非常困难? - Franklin
是的,它会起作用。对于任何量词,都存在一个实现限制为2147483647(请参见Integer.MAX_VALUE)。据我所知,这个限制适用于所有正则表达式实现。 - Bohemian

1
如果您不介意使用正则表达式的另一种方法,我建议使用.matcher:
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"));
}

输出:

a
b,b/
c/,/
d/,

ideone演示

这种方法将匹配除分隔逗号以外的所有内容(有点相反)。优点是它不依赖于先行断言。


谢谢。这是一个优雅的解决方案。然而,我不清楚非捕获组的使用是否必要。 - Franklin
@Franklin 绝对需要一个组,这样组 [^,/]+|/. 就可以通过 + 重复。好吧,你可以使用 ([^,/]+|/.),但那会在变量中存储一些东西,因此需要更多的内存。它并没有做什么特别的事情,但我更喜欢尽可能避免捕获组。如果你有很多事情要做,它们往往会放慢速度。 - Jerry
再次感谢Jerry。听起来很有道理。:-)那个组的另一个问题:我们不能只删除[^,/]部分的内部重复吗?(自从我玩正则表达式已经很长时间了...)我接受你的答案,因为它允许“无限”(当然不是真正的无限)数量的转义字符。您能告诉我这与String.split()相比是更快还是更慢吗? - Franklin
1
@Franklin 噢,对不起。我不知道如何在 C# 中计时函数(暂时还不知道 ^^;),但可能值得一提的是回顾子表达式通常很慢。 - Jerry
我的意思是:我们能不能只用 (?:[^,/]|/.)+ 替换 (?:[^,/]+|/.)+ 呢?或者说这样做有必要吗? - Franklin
显示剩余2条评论

0

我喜欢正则表达式,但是在这里手动编写代码不是更容易吗?

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

我确实已经手动完成了这个任务,但现在我特别想寻找一个正则表达式的解决方案。 - Franklin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接