一文入门正则表达式

187 阅读4分钟

正则表达式定义了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。本文使用JavaScript来进行常用正则表达式的说明,但它在我们的日常编程语言中都是通用的,只不过调用的具体API会有一些差别。

在JavaScript中,如果我们想要找一个txt类型的文件(也就是匹配一个.txt后缀的字符串),这个正则表达式该怎么写呢?

// 使用JavaScript定义的方式
var regex1 = /\.txt/g;
// 使用字符串的方式
var regex2 = new RegExp('\\.txt','g');

将regex1拆解成两部分:

第一个/后面跟的是正则表达式,它在哪里都是通用的;

第二个/后面跟的是当前正则表达式的行为。

转义字符

在分析正则表达式之前需要先了解一下转义字符:转义字符(Escape Character)是指在ASCII码和Unicode等字符集中的无法被键盘录入的字符,想要使用就得经过一种特别的方式表达出来。

比如,我们现在打算输出一个响铃符,但是这个字符都没有现成的文字代号,即无法用键盘上任何单个按键表示,就通过\a的方式来组合输出。

\a同时出现时不会输出\a的组合,而是会进行响铃,使用这种方式导致原来的字符改变了意义,所以就称为转义字符。

在字符串中,默认\就是转义的标志:当字符串中出现\时,它就会将后面的字符按照转移表的规定进行转义。

下面是转义字符表:

re_1

回到我们的第一个正则表达式,问题来了,匹配一个.txt的文件为什么要加\?

先拿regex1来看,它的正则表达式是\.txt,是因为在正则表达式中,.这个符号有着特殊的意义,它表示匹配除换行符以外的所有字符,所以我们就得使用转义字符\来告诉js,它究竟是单纯的符号匹配还是表通配符。

这个时候我们就更加疑惑了,第二种写法居然有两个\\,这又是为什么呢?

\\.txt

仔细分析一下上面的写法,\.txt是标准的正则表达式写法,但如果将其当成一个字符串,\就成了转义的标志,它会想要跟后面的字符转义而不是单纯的表达\的字面值。所以我们需要对\进行二次转义,让它就是一个单纯的\字符而不需要转义。

总结一下,第一个\是正则表达式的转义规则,要转义通配符;而第二个\则是字符串中的转义规则,要表示出\的字面值。

在了解了转义字符之后,我们就可以学习使用一些基础的正则表达式写法了:

() {} [] 的区别

[ ] 是定义匹配的字符范围,比如 [a-zA-Z0-9] 表示字符文本要匹配英文字符和数字。

[-az]则是单纯的表示三个字符。

{ } 一般用来表示匹配的长度,比如 [0-9]{3} 表示匹配三个数字,[0-9]{1,3} 表示匹配1~3个数字,[0-9]{3,} 表示匹配3个以上数字。

( ) 内的内容表示的是一个子表达式,( )本身不匹配任何东西,也不限制匹配任何东西,只是把括号内的内容作为同一个表达式来处理。

例如 (ab){1,3},就表示ab一起连续出现最少1次,最多3次。如果没有括号的话,ab{1,3},就表示a,后面紧跟的b出现最少1次,最多3次。

^ 与 $

^ 匹配一个字符串的开头,比如 ^a 就是匹配以字母a开头的字符串

^ 与[ ]连用时表示取反,[^abc]表示除去a/b/c以外的任意字符。

匹配一个字符串的结尾,比如b 匹配一个字符串的结尾,比如 b 就是匹配以字母b结尾的字符串

\d \s \w .

\d 匹配一个非负整数, 等价于 [0-9]

\s 匹配一个空白字符

\w 匹配一个英文字母或数字,等价于[0-9a-zA-Z]

. 匹配除换行符以外的任意字符,等价于[^\n]

* + ?

* 表示匹配前面元素0次或多次,相当于{0,}

+ 表示匹配前面元素1次或多次,相当于{1,}

? 表示匹配前面元素0次或1次,相当于{0,1}

$1

存放着正则表达式中最近的9个正则表达式的提取的结果,按序号拿取。该表达式与()配置使用可以实现局部取值的效果:

案例:将手机号转换成132****3344的形式
function telFormat(tel){
    tel = String(tel);
    return tel.replace(/(\d{3})(\d{4})(\d{4})/, function (rs, $1, $2, $3) {
       return $1 + '****'+ $3
    });
}
// Java实现
String phone = "13233448956";
phone = phone.replaceAll("(\\d{3})(\\d{4})(\\d{4})","$1****$3");
System.out.println(phone);

//Java中分组的值可以使用group来获取
String s = "11122223333";
Pattern r = Pattern.compile("(\\d{3})(\\d{4})(\\d{4})");
Matcher m = r.matcher(s);
//如果没有成功匹配,就不会进入if块
if (m.find( )) {
   System.out.println("Found value: " + m.group(0) );   //整个表达式
   System.out.println("Found value: " + m.group(1) );	//111
   System.out.println("Found value: " + m.group(2) );	//2222
   System.out.println("Found value: " + m.group(3) );	//3333
}

正则表达式行为

正则表达式支持以下三种行为:

g: 表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
i: 表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写;
m: 表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。

正则表达式的使用

正如我们开头所说,正则表达式被绝大多数编程语言支持,只要调用相应的API即可。此处我们来演示一个案例:

匹配两端有空白的字符串:
"   aaa   "

1.基于JavaScript

var str = "  aaa  ";
var regex1 = /(^\s+)|(\s+)$/g;
console.log(regex1.test(str))

str = str.replace(regex1,function(){
    return '';
});
console.log(str)

re_2

2.基于Java

String line = "   aaa   ";
String pattern = "(^\\s+)|(\\s+$)";
// Java中的正则表达式默认是全匹配(g)。
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(line);
if (m.find( )) {
   System.out.println("Found value: " + m.group(0) );
   System.out.println("Found value: " + m.group(1) );
   line = line.replaceAll(pattern,"");
   System.out.println(line);
} else {
   System.out.println("NO MATCH");
}

re_3

常见的应用场景

除去上面两个案例之外,正则表达式还有很多的应用场景:

1.提取url中的参数

//Java实现String url = "https://wuqingc.github.io?id=1&name=2";String pattern = "[?&](\\w+)=(\\w+)";Pattern r = Pattern.compile(pattern,'g');Matcher m = r.matcher(url);// 每次find()都会查找到一个匹配的子串,0代表当前匹配到的字串,1 2表示具体的分组内容while (m.find( )) {    System.out.println("Found value: " + m.group(0) );    System.out.println("Found value: " + m.group(1) );    System.out.println("Found value: " + m.group(2) );}

2.在字符串的指定位置插入

//Java实现String s = "1112222333";String str = "testInsert";s = s.replaceAll("(\\d{3})(\\d{4})(\\d{3})","$1"+str+"$2$3");System.out.println(s);

在日常的字符串处理过程中,多加考虑正则表达式的使用场景会使问题变得简化。