{m} {n}(“正好n次”两次)是如何工作的?
所以,不pipe怎么样,我都会用\d{1}{2}
这样的正则expression式。
从逻辑上讲,对我来说,这应该是:
(一个数字恰好一次)恰好两次,即一个数字恰好两次。
但事实上,它似乎只是“数字一次”(因此忽略了{2}
)。
String regex = "^\\d{1}{2}$"; // ^$ to make those not familiar with 'matches' happy System.out.println("1".matches(regex)); // true System.out.println("12".matches(regex)); // false
使用{n}{m,n}
或类似的结果可以看出类似的结果。
为什么会这样呢? 它是否在正则expression式/ Java文档中明确表示,还是只是Java开发人员在飞行中做出的决定,还是可能是一个错误?
或者它实际上是不被忽视的,它实际上意味着其他的东西呢?
这并不重要,但它不是全面的正则expression式, Rubular按我所期望的做了。
注 – 标题主要是为了想知道它是如何工作(而不是为什么)的用户的可search性。
当我使用Java正则expression式在RegexBuddy中input正则expression式时,它会显示以下消息
量词前面必须有一个可重复的标记«{2}»
更改正则expression式以显式使用分组^(\d{1}){2}
可解决该错误,并按预期工作。
我假设java的正则expression式引擎简单地忽略了错误/expression式,并与迄今已编译的工作。
编辑
在@ piet.t的答案中引用IEEE标准似乎支持这一假设。
编辑2 (荣誉@fncomp)
为了完整性,通常使用(?:)
来避免捕获组。 完整的正则expression式然后变成^(?:\d{1}){2}
IEEE-Standard 1003.1说:
多个相邻复制符号('*'和区间)的行为会产生未定义的结果。
所以每个实现都可以随心所欲,只是不要依赖任何具体的东西。
科学方法:
点击模式在regexplanet.com上查看示例,然后单击绿色的Javabutton 。
- 您已经显示
\d{1}{2}
匹配"1"
,并且不匹配"12"
,所以我们知道它不会被解释为(?:\d{1}){2}
。 - 尽pipe如此,1是一个无聊的数字,
{1}
可能会被优化,让我们尝试一些更有趣的事情:
\d{2}{3}
。 这仍然只匹配两个字符(不是六个),{3}
被忽略。 - 好。 有一个简单的方法来看看一个正则expression式引擎做什么。 它捕获?
让我们试试(\d{1})({2})
。 奇怪的是,这个工程。 第二组$2
捕获空string。 - 那么为什么我们需要第一组呢?
({1})
怎么样? 仍然有效。 - 只是
{1}
? 那里没问题。
看起来Java在这里有点奇怪。 -
大! 所以
{1}
是有效的。 我们知道Java把*
和+
扩展到{0,0x7FFFFFFF}
和{1,0x7FFFFFFF}
,所以*
或者+
工作吗? 没有:在索引0附近悬挂元字符“+”
+
^validation必须在
*
和+
扩展之前进行。
在规范中我没有find任何解释的东西, 看起来量词必须至less出现在字符,括号或括号之后。
大多数这些模式被认为是无效的其他正则expression式,并有一个很好的理由 – 他们没有意义。
起初我很惊讶,这不会抛出一个PatternSyntaxException
。
我不能根据任何事实来回答,所以这只是一个有教养的猜测:
"\\d{1}" // matches a single digit "\\d{1}{2}" // matches a single digit followed by two empty strings
我从来没有见过{m}{n}
语法。 似乎这个Rubular页面上的正则expression式引擎将{2}
量词应用到最小的可能标记之前 – 这是\\d{1}
。 为了在Java(或大多数其他正则expression式引擎)中模拟这一点,您需要像这样对\\d{1}
进行分组:
^(\\d{1}){2}$
在这里看到它的行动 。
编译正则expression式的结构
对于"^\\d{1}{2}$"
或"{1}"
的情况, Kobi的回答是关于Java正则expression式(Sun / Oracle实现)的行为。
以下是"^\\d{1}{2}$"
的内部编译结构:
^\d{1}{2}$ Begin. \A or default ^ Curly. Greedy quantifier {1,1} Ctype. POSIX (US-ASCII): DIGIT Node. Accept match Curly. Greedy quantifier {2,2} Slice. (length=0) Node. Accept match Dollar(multiline=false). \Z or default $ java.util.regex.Pattern$LastNode Node. Accept match
看着源代码
从我的调查,这个错误可能是由于这个事实{
私人方法sequence()
没有正确检查。
方法sequence()
调用atom()
来parsingprimefaces,然后通过调用closure()
将定量符附加到primefaces上,并将所有的primefaces与闭合一起链接成一个序列。
例如,给定这个正则expression式:
^\d{4}a(bc|gh)+d*$
然后,对sequence()
的顶层调用将接收^
, \d{4}
, a
, (bc|gh)+
, d*
, $
的编译节点并将它们链接在一起。
考虑到这个想法,让我们看一下从OpenJDK 8-b132 (Oracle使用相同的代码库)复制的sequence()
的源代码:
@SuppressWarnings("fallthrough") /** * Parsing of sequences between alternations. */ private Node sequence(Node end) { Node head = null; Node tail = null; Node node = null; LOOP: for (;;) { int ch = peek(); switch (ch) { case '(': // Because group handles its own closure, // we need to treat it differently node = group0(); // Check for comment or flag group if (node == null) continue; if (head == null) head = node; else tail.next = node; // Double return: Tail was returned in root tail = root; continue; case '[': node = clazz(true); break; case '\\': ch = nextEscaped(); if (ch == 'p' || ch == 'P') { boolean oneLetter = true; boolean comp = (ch == 'P'); ch = next(); // Consume { if present if (ch != '{') { unread(); } else { oneLetter = false; } node = family(oneLetter, comp); } else { unread(); node = atom(); } break; case '^': next(); if (has(MULTILINE)) { if (has(UNIX_LINES)) node = new UnixCaret(); else node = new Caret(); } else { node = new Begin(); } break; case '$': next(); if (has(UNIX_LINES)) node = new UnixDollar(has(MULTILINE)); else node = new Dollar(has(MULTILINE)); break; case '.': next(); if (has(DOTALL)) { node = new All(); } else { if (has(UNIX_LINES)) node = new UnixDot(); else { node = new Dot(); } } break; case '|': case ')': break LOOP; case ']': // Now interpreting dangling ] and } as literals case '}': node = atom(); break; case '?': case '*': case '+': next(); throw error("Dangling meta character '" + ((char)ch) + "'"); case 0: if (cursor >= patternLength) { break LOOP; } // Fall through default: node = atom(); break; } node = closure(node); if (head == null) { head = tail = node; } else { tail.next = node; tail = node; } } if (head == null) { return end; } tail.next = end; root = tail; //double return return head; }
注意行throw error("Dangling meta character '" + ((char)ch) + "'");
。 这是错误发生的地方,如果+
, *
?
是悬挂的,不是前面的标记的一部分。 正如你所看到的, {
不是在抛出错误的情况下。 实际上,它并不存在于sequence()
中的案例列表中,编译过程将default
情况下直接转换为atom()
。
@SuppressWarnings("fallthrough") /** * Parse and add a new Single or Slice. */ private Node atom() { int first = 0; int prev = -1; boolean hasSupplementary = false; int ch = peek(); for (;;) { switch (ch) { case '*': case '+': case '?': case '{': if (first > 1) { cursor = prev; // Unwind one character first--; } break; // Irrelevant cases omitted // [...] } break; } if (first == 1) { return newSingle(buffer[0]); } else { return newSlice(buffer, first, hasSupplementary); } }
当进程进入atom()
,由于它立即遇到{
,它从switch
和for
循环中断开,并创build一个长度为0的新片 (长度从0开始)。
当这个片返回时,量化器被closure()
parsing,导致我们看到的东西。
比较Java 1.4.0,Java 5和Java 8的源代码, sequence()
和atom()
的源代码似乎没有太多变化。 看来这个bug从一开始就一直存在。
正则expression式的标准
引用IEEE-Standard 1003.1 (或POSIX标准)的最高票数的答案与讨论无关,因为Java 没有实现 BRE和ERE。
根据标准,有很多语法会导致未定义的行为,但在许多其他正则expression式中却是定义良好的行为(尽pipe他们是否同意是另一回事)。 例如,根据标准, \d
是未定义的,但它匹配很多正则expression式中的数字(ASCII / Unicode)。
可悲的是,在正则expression式语法上没有其他标准。
然而,Unicode正则expression式有一个标准,它着重于一个Unicode正则expression式引擎应具有的function。 Java Pattern
类或多或less地实现了UTS#18:Unicode正则expression式和RL2.1(尽pipe非常麻烦)中描述的1级支持。
我猜测在{}
定义中是“回顾寻找有效的expression式(不包括我自己 – {}
”),所以在你的例子中, }
和{
之间没有任何关系。
无论如何,如果你用圆括号包装它,它会按照你的预期工作: http : //refiddle.com/gv6 。