Regular Expression

 

正则表达式的知识点:

关于正则的底层实现原理:

实现正则引擎的两种方式:

DFA (Deterministic Final Automata) 确定型有穷自动机

NFA (Nondeterministic Finite Automata) 不确定型有穷自动机

NFA 对应的是正则表达式主导的匹配,而 DFA 对应的是文本主导的匹配

DFA从匹配文本入手,从左到右,每个字符不会匹配两次,它的时间复杂度是多项式的,所以通常情况下,它的速度更快,但支持的特性很少,不支持捕获组、各种引用等等;而NFA则是从正则表达式入手,不断读入字符,尝试是否匹配当前正则,不匹配则吐出字符重新尝试,通常它的速度比较慢,最优时间复杂度为多项式的,最差情况为指数级的。但NFA支持更多的特性,因而绝大多数编程场景下(包括java,js),我们面对的是NFA。

 

关于正则匹配的方式:

regex = "sun"
content = "Today is sunday"

NFA 是以正则表达式为基准去匹配的。也就是说,NFA 自动机会读取正则表达式的一个一个字符,然后拿去和目标字符串匹配,匹配成功就换正则表达式的下一个字符,否则继续和目标字符串的下一个字符比较。

首先是拿第一个匹配符:s  去和字符串content进行比较,先与字符串的第一个 T 进行匹配,不符合则比较字符串的第二个字符 o 不匹配,则继续往下走。直到匹配符 s 与 字符串的 s 匹配成功之后, 那么NFA 就读取匹配符的第二个字符 u 。

读取到匹配符的第二个字符串 u 之后,与字符串上次匹配成功的 s 之后的位置开始匹配。 u 匹配成功之后。则NFA继续读取匹配符的下一个字符。以此类推。

读取到匹配符的第三个 字符串 d 之后,继续与字符串上次匹配成功 u 之后的位置。匹配成功之后,继续读取匹配符的下一个字符。发现没有了,则匹配结束。

 

关于匹配回溯的情况:

regex = "ro{1,3}t"

content = "root"

NFA自动机是贪婪的,我们知道在这里正则表示的意思是,目标字符具有 r + o (1-3个) + t 。对于NFA的贪婪性,简单的理解就是NFA在这里会尽量多的匹配 o 。这里就会涉及到回溯。

还是跟以上的步骤一样,NFA先读取匹配字符的第一个字符 r ,与content字符串进行匹配,第一个匹配成功,则继续读取第二个字符 o 。

o 会与content字符串的第二位进行匹配,匹配成功。此时NFA不会再去读取正则的第三个字符,而是继续拿 o 去与content 字符进行匹配,匹配又成功,因为这里的贪婪的最大值为3. 则还是会继续去拿 o 与之匹配,

当匹配字符 o 与content的第四个字符串比较 发现不匹配了,这个时候便会发生回溯。

什么是回溯的操作呢? 回溯就是当发现content的第四个字符 t 与 o 不匹配时,但是这个 t 已经吐出去了(指针已经到了t 的位置),那么这时候,指针就会回溯到第三个字符的位置也就是 t 前一个的位置。

回溯之后,NFA再读取 t 字符与content的第四个字符进行排匹配。匹配成功之后,并且发现没有下一个字符了,则匹配结束。

  

关于matcher.group() 的方法:

  // groups[] int 型的一维数组
  // groupCount 可以看作是返回的大括号数量 不包含group(0)
  // 这里有个有意思的地方是 当括号小于10 的时候,默认groups长度为20。下面的代码能解释此时包含了group(0)


  public String group(int group) { if (first < 0) throw new IllegalStateException("No match found"); if (group < 0 || group > groupCount()) throw new IndexOutOfBoundsException("No group " + group); if ((groups[group*2] == -1) || (groups[group*2+1] == -1)) return null; return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString(); }


/**
* Returns the number of capturing groups in this matcher's pattern.
*
* <p> Group zero denotes the entire pattern by convention. It is not
* included in this count.
*
* <p> Any non-negative integer smaller than or equal to the value
* returned by this method is guaranteed to be a valid group index for
* this matcher. </p>
*
* @return The number of capturing groups in this matcher's pattern
*/
  返回的是
  public int groupCount() {
return parentPattern.capturingGroupCount - 1;
  }

  我们知道groups是一个一维数组,那么底层是如何执行的呢?

  首先底层有一行代码: 与定义的括号数量做对比

  int parentGroupCount = Math.max(parent.capturingGroupCount, 10);

  

 

  那么groups数组是如何记录的呢?

  每找到一个符合规则的元素时的执行情况: 这里我定义的正则为(\\d\\d)(\\d\\d)  以及String为:"1998 ......."

  这里正则表达式分组为两组:

  1. 根据指定的规则,定位满足规则的字符串(1998)

  2. 找到后,记录到matcher对象的属性int[] groups

    2.1 首先是group[0] 和 group[1] 进行记录,这个记录的是 整个匹配的结果(1998)在 在目标字符串中对应的索引 +1

      这里的group[0] = 0  group[1] = 4

    2.2 进行内部组的匹配: 这里两个组,那么就是 组1 和 组2

    组1 对应的groups分配的记录索引的位置为:groups[2] = 0 和  group[3] = 2

    组2 对应的groups分配的记录索引的位置为:groups[4] = 2 和  group[5] = 4

  3. 同时记录oldLast的值(= 4)子字符串结束的索引+1 的值 即 4, 这是为了下次执行find方法时,直接从4开始匹配

 

元字符 - 限定符 : 用于指定其前面的字符或组合项连续出现的多少次
符号 含义 示例 说明 匹配输入
* 指定字符重复出现0次或者n次 (0到多) (abc)* 仅包含任意个abc的字符串,等效于、w abc | abcabcabc
+ 指定字符重复1次或者n次 (1到多) m+(abc)* 以至少1个m开头,后接任意个abc的字符串 m | mabc | mabcabc... 
指定字符重复0次或者1次 (0到1) m+abc? 以至少一个1个m开头,后接ab或者abc的字符串(注:? 影响该符号左边最近的字符,abc在这里为三个字符。 mab | mabc | mmab | mabcc...
{n} 只能输入n个字符 (限定个数n) [abcde]{3} 由abcde字符串组合的任意长度为3的字符串 abc | bcd | abe
{n,} 指定至少n个匹配 [abcd]{3,} 由abcd中字母组合的任意长度不小于3的字符串 aac | abcd | bccd
{n,m} 指定至少n个但是不能多于m个匹配 [abcde]{3,6} 由abcd中字母组合的任意长度不小于3,且不大于6的字符串 abce | aaaa | bceddd

 

 

 

 

 

 

 

 

 

 

 

 

 

举例: Java中默认为贪婪匹配,即尽可能匹配多的元素.     *  + 都是贪婪的

 

 这里     :adeadadaad 满足条件的字符串只有两个!

 

贪婪匹配 -> 非贪婪匹配 : 只需要在其后面加上 ? 就能实现匹配最先值

元字符-定位符 :规定要匹配的字符串出现的位置,eg:出现在开始还是结束的位置
符号   含义 示例 说明 匹配输入
^ 指定起始字符 ^[0-9]+[a-z]* 以至少1个数字开头,后面任意个小写字母的字符串 123 | 6aa | 121lsls
$ 指定结束字符 ^[0-9]\\-[a-z]+$ 以1个数字开头后接连字符“ - ”,并以至少1个小写字母结尾的字符串   1-q | 1-sada
\b 匹配目标字符串的边界 Jakob\b 字符串的边界指的是字串间有空格,或者是目标字符串的结束位置 This is Jakob | JJJakob
\B 匹配目标字符串的非边界 Jakob\B 和\b的含义相反,出了是空格、结束的位置 在句子中出现了匹配字符就能匹配 JJJakobHH | Jakobishandsome

 

 

 

 

 

 

 

 

正则表达式语法:

分组
常用分组构造形式   说明
(pattern) 非命名捕获,捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1开始自动编号。
(?<name>pattern) 命名捕获,将匹配俄子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包含任何标点符号,并且不能以数字开头。可以使用单引号替代尖括号,eg:(?'name')

 

 

 

 

 

 

非捕获分组 
常用分组构造形式 说明
(?:pattern) 匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配(简单的理解为他不是group) eg:'studen(?:t|ts)' 相比 'student | students' 跟简洁
(?=pattern) 正向肯定预查(look ahead positive assert)它是非捕获匹配,eg:"Windows(?=95|98|NT|2000)"能匹配"Windows2000"中的"Windows",但不能匹配"Windows3.1"中的"Windows"。
(?!pattern) 正向否定预查(negative assert)  eg:"Windows(?!95|98|NT|2000)"能匹配"Windows3.1"中的"Windows",但不能匹配"Windows2000"中的"Windows"。

 

 

 

 

 

 

 

 

 

相关博客: https://www.cnblogs.com/study-everyday/p/7426862.html

 

https://blog.csdn.net/li20081006/article/details/21999779

https://www.cnblogs.com/boundless-sky/p/7597631.html

posted @ 2021-06-10 20:25  Jakob~  阅读(150)  评论(0)    收藏  举报