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 满足条件的字符串只有两个!
贪婪匹配 -> 非贪婪匹配 : 只需要在其后面加上 ? 就能实现匹配最先值

| 符号 | 含义 | 示例 | 说明 | 匹配输入 |
| ^ | 指定起始字符 | ^[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

浙公网安备 33010602011771号