带有后引用的Java String.replaceAll()

时间:2016-03-28 17:23:04

标签: java regex

有一个Java Regex问题: 给定一个字符串,如果" *"在字符串的开头或结尾处,保留它,否则,将其删除。 例如:

  1. * - > *
  2. ** - > **
  3. ******* - > **
  4. *abc**def* - > *abcdef*
  5. 答案是:

    str.replaceAll("(^\\*)|(\\*$)|\\*", "$1$2");
    

    我在我的机器上尝试了答案,但它确实有效。但我不知道它是如何运作的。

    根据我的理解,所有匹配的子字符串应替换为$1$2。但是,它的工作原理如下:

    1. (^\\*)已替换为$1
    2. (\\*$)已替换为$2
    3. \\*替换为空。
    4. 有人可以解释它是如何工作的吗?更具体地说,如果表达式之间存在|String.replaceAll()如何使用后向引用?

      提前谢谢。

5 个答案:

答案 0 :(得分:21)

我会尝试解释正则表达式中发生的事情。

str.replaceAll("(^\\*)|(\\*$)|\\*", "$1$2");

$1代表第一组(^\\*) $2代表第二组(\\*$)

当您致电str.replaceAll时,您实际上是在捕获两个群组以及其他所有内容,但在更换时,将捕获的文本替换为两个群组中捕获的内容。

示例:*abc**def* --> *abcdef*

发现正则表达式以*开头的字符串,它将放入$1组,接下来它会继续查找,直到它在组末尾找到*并将其存储在{{1} }}。现在,当替换它时将消除除#2*

中存储的$1之外的所有$2

有关详细信息,请参阅Capture Groups

答案 1 :(得分:2)

您可以在正则表达式中使用外观:

String repl = str.replaceAll("(?<!^)\\*+(?!$)", "");

RegEx Demo

RegEx分手:

(?<!^)   # If previous position is not line start
\\*+     # match 1 or more *
(?!$)    # If next position is not line end
OP的正则表达式是:

(^\*)|(\*$)|\*

它使用2个捕获的组,一个用于*,另一个用于*,并且在替换时使用后向引用。这可能在这里起作用,但是对于更大的字符串来说,如# of steps taken in this demo中所显示的那样更慢。这是使用环视的 209 vs 48 步骤。

OP正则表达式的另一个较小的改进是使用量词

(^\*)|(\*$)|\*+

答案 2 :(得分:1)

好吧,让我们首先看看你的正则表达式(^\\*)|(\\*$)|\\* - 它匹配每个*,如果它在开始时,它被捕获到组1中,如果它在结尾,它被捕获到第2组 - 每个其他*匹配,但不会被放入任何组。

替换模式$ 1 $ 2将每个匹配替换为组1和组2的内容 - 因此,如果在匹配的开头或结尾有*,则其中一个组的内容为那个*本身因而被它自己取代。对于所有其他匹配,组仅包含空字符串,因此匹配的*将替换为此空字符串。

您的问题可能是$ 1 $ 2不是字面替换,而是对捕获组的反向引用。

答案 3 :(得分:0)

根据Javadoc:

  

请注意,替换字符串中的反斜杠()和美元符号($)可能会导致结果与将其视为文字替换字符串时的结果不同;见Matcher.replaceAll。如果需要,使用Matcher.quoteReplacement(java.lang.String)来抑制这些字符的特殊含义。

你的正则表达式:"(^\\*)|(\\*$)|\\*"

删除引号和String转义后:(^\*)|(\*$)|\*

有三个部分,由管道|分隔。管道意味着OR,这意味着replaceAll()将其替换为第二部分中的内容:$1$2。基本上,第一部分&gt;&gt; $ 1,第二个&gt;&gt; $ 2,第三个&gt;&gt; ""。注意&#34;第一部分&#34; == 1美元,等等......所以它在技术上没有被替换。

1 (^\*)是一个捕获组(第一个)。 ^锚定到字符串start。 \*匹配*,但需要转义\

2 (\*$)再次,一个捕获组(第二个)。这里的区别在于它以$

结尾 与之前一样的

3 \*匹配文字*

你需要了解的关于正则表达式的事情是,如果它匹配,它将始终采用第一条路径。虽然字符串开头和结尾的*可以与第三部分匹配,但它们会匹配第一部分或第二部分。

答案 4 :(得分:0)

其他人给出了非常好的答案,所以我不会重复这些答案。当您正在努力理解诸如此类的问题时,建议是临时向替换字符串添加分隔符,以便清楚每个阶段发生的情况。

e.g。使用"<$1|$2>"这将得到<x|y>的结果,其中x为$ 1,y为$ 2

String str = "*ab**c*d*";
str.replaceAll("(^\\*)|(\\*$)|\\*", "<$1|$2>");

结果是:<*|>ab<|><|>c<|>d<|*>

因此对于第一个星号,$ 1 = *和$ 2为空,因为(^\\*)匹配。

对于中间字符串星号,$ 1和$ 2都是空的,因为两个捕获组都不匹配。

对于最终的星号,$ 1为空,$ 2为*,因为(^\\*)不匹配,但(\\*$)不匹配。