【问题标题】:Regex and escaped and unescaped delimiter正则表达式和转义和非转义分隔符
【发布时间】:2011-12-15 15:54:56
【问题描述】:

this相关的问题

我有一个字符串

a\;b\\;c;d

在Java中看起来像

String s = "a\\;b\\\\;c;d"

我需要用分号按照以下规则进行分割:

  1. 如果分号前面有反斜杠,则不应将其视为分隔符(在 ab 之间)。

  2. 如果反斜杠本身被转义,因此不转义分号,该分号应该是分隔符(在 bc 之间)。

因此,如果分号前面有零个或偶数个反斜杠,则应将分号视为分隔符。

例如上面,我想得到以下字符串(java编译器的双反斜杠):

a\;b\\
c
d

【问题讨论】:

  • 双反斜杠在哪里?走了?
  • 我不确定这是不是你想要的正则表达式
  • 我也不确定正则表达式是否是完成这项任务的最佳工具。但是您选择忽略我在下面的回答;-/

标签: java regex escaping backslash


【解决方案1】:

你可以使用正则表达式

(?:\\.|[^;\\]++)*

匹配非转义分号之间的所有文本:

List<String> matchList = new ArrayList<String>();
try {
    Pattern regex = Pattern.compile("(?:\\\\.|[^;\\\\]++)*");
    Matcher regexMatcher = regex.matcher(subjectString);
    while (regexMatcher.find()) {
        matchList.add(regexMatcher.group());
    } 

说明:

(?:        # Match either...
 \\.       # any escaped character
|          # or...
 [^;\\]++  # any character(s) except semicolon or backslash; possessive match
)*         # Repeat any number of times.

所有格匹配 (++) 对于避免由于嵌套量词而导致的灾难性回溯非常重要。

【讨论】:

  • 它也返回空字符串,所以我得到了[a\;b\\, , c, , d, ]。除了检查 group() 的返回值之外,是否有可能以某种方式阻止它?
  • 是的,使用 + 而不是 *,您可以摆脱空字符串
  • 奇怪,它在我的测试中没有这样做(虽然在 RegexBuddy 中)。好吧,如果您不想要空匹配,请将* 更改为+,但是您也不会像a;;b 那样得到“真正的”空匹配。
  • 是的,真正的空匹配很好。
  • FYI 边缘情况:当最后一个字段以转义字符 '\' 结尾,或者输入只是一个唯一的转义字符时,最后一个转义字符会丢失,即 "a\" => [ “一种”, ””, ””]。以下似乎解决了这种极端情况"(?:\\\\(.|$)|[^;\\\\]++)*",但不确定是否会创建另一个。我的表达(到目前为止)也解决了假空字段但保留真正的空字段是"(?&lt;=(?:^|;))(?:\\\\(?:.|$)|[^;\\\\]++)*"。谢谢你的好主意。
【解决方案2】:
String[] splitArray = subjectString.split("(?<!(?<!\\\\)\\\\);");

这应该可行。

解释:

// (?<!(?<!\\)\\);
// 
// Assert that it is impossible to match the regex below with the match ending at this position (negative lookbehind) «(?<!(?<!\\)\\)»
//    Assert that it is impossible to match the regex below with the match ending at this position (negative lookbehind) «(?<!\\)»
//       Match the character “\” literally «\\»
//    Match the character “\” literally «\\»
// Match the character “;” literally «;»

因此,您只需匹配前面没有正好一个 \ 的分号。

编辑:

String[] splitArray = subjectString.split("(?<!(?<!\\\\(\\\\\\\\){0,2000000})\\\\);");

这将处理任何奇数个 .如果您有超过 4000000 个 \,它当然会失败。编辑答案的解释:

// (?<!(?<!\\(\\\\){0,2000000})\\);
// 
// Assert that it is impossible to match the regex below with the match ending at this position (negative lookbehind) «(?<!(?<!\\(\\\\){0,2000000})\\)»
//    Assert that it is impossible to match the regex below with the match ending at this position (negative lookbehind) «(?<!\\(\\\\){0,2000000})»
//       Match the character “\” literally «\\»
//       Match the regular expression below and capture its match into backreference number 1 «(\\\\){0,2000000}»
//          Between zero and 2000000 times, as many times as possible, giving back as needed (greedy) «{0,2000000}»
//          Note: You repeated the capturing group itself.  The group will capture only the last iteration.  Put a capturing group around the repeated group to capture all iterations. «{0,2000000}»
//          Match the character “\” literally «\\»
//          Match the character “\” literally «\\»
//    Match the character “\” literally «\\»
// Match the character “;” literally «;»

【讨论】:

  • 这对于a\\\;b;c 和其他带有两个以上反斜杠的情况会失败。
  • 有人可以解释投票否决吗?除非我遗漏了一些明显的东西?
  • 我不知道,不是我。也许嵌套的反向引用有点太复杂了?
  • 这也不是我,但不要指望我投赞成票。 ;) {0,many} hack 是不可信的,因为 Java 的可变宽度后向支持是出了名的错误。但即使在 .NET 中我也不会使用这种方法,因为它对后视完全没有任何限制。像 Tim 的正匹配方法更易读、更可靠、更便携(所有格量词不是必需的)。
  • @AlanMoore 同意。但这并不意味着解决方案是错误的。
【解决方案3】:

我不相信用任何正则表达式来检测这些情况。我通常会为这些事情做一个简单的循环,我会使用C 来绘制它,因为它是很久以前我最后一次接触Java ;-)

int i, len, state;
char c;

for (len=myString.size(), state=0, i=0; i < len; i++) {
    c=myString[i];
    if (state == 0) {
       if (c == '\\') {
            state++;
       } else if (c == ';') {
           printf("; at offset %d", i);
       }
    } else {
        state--;
    }
}

优点是:

  1. 您可以在每个步骤上执行语义操作。
  2. 很容易将其移植到另一种语言。
  3. 您不需要仅仅为了这个简单的任务就包含完整的正则表达式库,这增加了可移植性。
  4. 它应该比正则表达式匹配器快很多。

【讨论】:

    【解决方案4】:

    此方法假定您的字符串中不会包含char '\0'。如果这样做,您可以使用其他字符。

    public static String[] split(String s) {
        String[] result = s.replaceAll("([^\\\\])\\\\;", "$1\0").split(";");
        for (int i = 0; i < result.length; i++) {
            result[i] = result[i].replaceAll("\0", "\\\\;");
        }
        return result;
    }
    

    【讨论】:

      【解决方案5】:

      这是我认为的真正答案。 在我的情况下,我尝试使用 | 进行拆分,转义字符是 &amp;

          final String regx = "(?<!((?:[^&]|^)(&&){0,10000}&))\\|";
          String[] res = "&|aa|aa|&|&&&|&&|s||||e|".split(regx);
          System.out.println(Arrays.toString(res));
      

      在这段代码中,我使用 Lookbehind 来转义 & 字符。 注意后面的look必须有最大长度。

      (?<!((?:[^&]|^)(&&){0,10000}&))\\|
      

      这表示任何|,除了((?:[^&amp;]|^)(&amp;&amp;){0,10000}&amp;)) 之后的那些,这部分表示任何奇数个&amp;s。 (?:[^&amp;]|^) 部分很重要,以确保您将 | 后面的所有 &amp;s 计数到开头或其他一些字符。

      【讨论】:

        猜你喜欢
        • 2014-04-27
        • 1970-01-01
        • 2017-08-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多