微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

正则指引之字符组

普通字符组

字符组就是一组字符,在正则表达式中,它表示“在同一个位置可能出现的各种字符”,其写法是在一对方括号 [ ] 之间列出所有可能出现的字符,简单的字符组比如 [ab],[314],[#.?] 。在解决一些常见问题时,使用字符组可以大大简化操作。下面举“匹配数字字符”的例子来说明(java):

......
charStr.matches("[0123456789]");

charStr是需要判断的字符串,而 [0123456789] 则是以字符串形式给出的正则表达式,它是一个字符组,表示“这里可以是0,1,2,3,4,5,6,7,8,9中的任意一个字符。只要charStr与其中任何一个字符相同即为匹配”。

不同语言使用正则表达式的方法也不相同。如果仔细观察会发现Java,.Net,Python,PHP中的正则表达式,都要以字符串形式给出,两端都有双引号"",而ruby和javascript中的正则表达式则不必如此,只在首尾有两个斜线字符/。这也是不同语言中使用正则表达式的不同之处。

字符组中的字符排列顺序不影响字符组的功能,出现重复字符影响,所以 [0123456789] 完全等价于 [9876543210] ,[9987120934556]。不过,代码总是要容易编写,方便阅读,正则表达式也是一样,所以一般并不推荐在字符组中出现重重字符。而且,还应该让字符组中的字符排列更符合谁知习惯,比如 [0123456789]。为此,正则表达式提供了范围表示法(range),它更直观,能进一步简化字符组。

所谓“-范围表示法”,就是用 [x-y] 的形式表示x到y整个范围内的字符,省去一一列出的麻烦,这样 [0123456789]就可以表示为 [0-9]。你可能会问,“-范围表示法”的范围是如何确定的?为什么要写作 [0-9],而不写作 [9-0]?要回答这个问题,必须了解范围表示法的实质。在字符组中,- 表示的范围,一般是根据字符对应的码值(Code Point,也就是字符在对应编码表中的编码的数值)来确定的,码值小的字符在前,码值大的字符在后。在ASCII编码中(包括各种兼容ASCII的编码中),字符0的码值是48(十进制),字符9的码值是57,所以[0-9]等价于[0123456789],而[9-0]则是错误的范围,因为9的码值大于0,所以会报错。如果知道0~9的码值是48~57,a~z的码值是97~122,A~Z的码值是65~90,能不能用 [0~z]统一表示数字字符,小写字母,大写字母?答案是勉强可以,但不推荐这么做。根据惯例,字符组的范围表示法都表示一类字符(数字字符是一类,字母字符也是一类),所以虽然[0-9],[a-z]都是很好理解的。

在字符组中可以同时并列多个“-范围表示法”,字符组 [0-9a-zA-Z] 可以匹配数字,大写字母或小写字母;字符组 [0-9a-fA-F] 可以匹配数字,大,小写形式的a~f,它可以用来验证十六进制字符。

在不少语言中,还可以用转义序列 \xhex 来表示一个字符,其中 \x固定前缀,表示转义序列的开头,num是字符对应的码值,是一个两位的十六进制数值。比如字符A的码值是41(十进制则为65),所以也可以用 \x41表示。

字符组中有时会出现这种表示法,它可以表示一些难以输入或者难以显示的字符,比如 \x7F;也可以用来方便地表示某个范围,比如所有ASCII字符对应的字符组就是 [\x00-\x7F]。这种表示法很重要,依靠这种表示法可以很方便地匹配所有的中文字符(后面再讲)。

元字符与转义

在上面的例子里,字符组中的横线 - 并不能匹配横线字符,而是用来表示范围,这类字符叫做元字符Meta-character)。字符组的开方括号[,闭方括号]都算元字符。在匹配中,它们有着特殊的意义。但是,有时候并不需要表示这些特殊意义,只需要表示普通字符(比如“我就想表示横线字符 - ”),此时就必须做特殊处理

先来看字符组中的 -,如果它紧邻着字符组中的开方括号 [,那么它就是普通字符,其他情况下都是元字符而对于其他元字符,取消特殊含义的做法都是转义,也就是在正则表达式中的元字符之前加上反斜线字符 \

如果要在字符组内部使用横线 - ,最好的办法是将它排列在字符组的最开头。[-09]就是包含三个字符 -,0,9的字符组;[0-9]是包含0~9这10个字符的字符组,[-0-9]则是由“-范围表示法”0~9和横线 - 共同组成的字符组,它可以匹配11个字符。

//作为普通字符
Stringtext="-";
Patternp=Pattern.compile("[-09]");
Matcherm=p.matcher(text);
System.out.println(m.matches());
//作为元字符
Stringtext="-";
Patternp=Pattern.compile("[0-9]");
Matcherm=p.matcher(text);
System.out.println(m.matches());
//转义后作为普通字符
Stringtext="-";
Patternp=Pattern.compile("[0\\-9]");
Matcherm=p.matcher(text);
System.out.println(m.matches());

仔细观察会发现,在正文里说“在正则表达式中的元字符之前加上反斜线字符 \ ”,而在代码里写的却不是 [0\-9],而是 [0\\-9]。这并不是错误。因为在这段程序里,正则表达式是以字符串(String)的方式提供的,而字符串本身也有关于转义的规定(你或许记得,在字符串中有 \n,\t之类的转义序列)。上面说的“正则表达式”,其实是经过“字符串转义处理”之后的字符串的值,正则表达式 [0\-9]包含6个字符:[,0,\,-,9,],在字符串中表达这6个字符;但是在源代码里,必须使用7个字符:\需要转义成\\,因为处理字符串时,反斜线和之后的字符会被认为是转义序列(Escape Sequence),比如 \n,\t,都是合法的转义序列,然而 \- 不是

继续看转义,如果希望在字符组中列出闭方括号 ],比如 [012]345],就必须在它之前使用反斜线转义,写成 [012\]345],否则正则表达式将]与最近的[匹配,这个表达式就成了“字符组[012]加上4个字符345]”,它能匹配的是字符串0345]或1345]或2345],却不能匹配 ]。

//未转义的]
Stringtext="0345]";
Patternp=Pattern.compile("[012]345]");
Matcherm=p.matcher(text);
System.out.println(m.matches());
//转义的]
Stringtext="]";
Patternp=Pattern.compile("[012\\]345]");
Matcherm=p.matcher(text);
System.out.println(m.matches());

除去字符组内部的 - ,其他元字符的转义都必须在字符之前添加反斜线,[ 的转义也是如此。如果只希望匹配字符串 [012]是不行的,因为这会被识别为一个字符组,它只能匹配0,1,2这三个字符中的任意一个,而必须转义,把正则表达式写作 \[012],请注意,只有开方括号 [ 需要转义,闭方括号 ] 不需要转义

//取消其他元字符的特殊含义
Stringtext="[012]";
Patternp=Pattern.compile("\\[012]");
Matcherm=p.matcher(text);
System.out.println(m.matches());

排除型字符组

在方括号[...]中列出希望匹配的所有字符,这种字符组叫做“普通字符组”,它的确非常方便。不过,也有些问题是普通字符组不能解决的。

给定一个由两个字符构成的字符串str,要判断这两个字符是否都是数字字符,可以用[0-9][0-9]来匹配。但是,如果要求判断的是这样的字符串——第一个字符不是数字字符,第二个字符才是数字字符(比如 A8,x6)——应当如何办?数字字符的匹配很好处理,用[0-9]即可;“不是数字”则很难办——不是数字的字符太多了,全部列出几乎不可能,这时就应当使用排除型字符组。排除型字符组(Negated Character Class)非常类似普通字符组 [...],只是在开方括号 [ 之后紧跟一个脱字符 ^ ,写作 [^...],表示“在当前位置,匹配一个没有列出的字符”。所以[^0-9]就表示“0~9之外的字符”,也就是“非数字字符”。那么上面那个问题就可以用[^0-9][0-9]解决了。例:

//使用排除型字符组
Stringtext="A8";
Patternp=Pattern.compile("[^0-9][0-9]");
Matcherm=p.matcher(text);
System.out.println(m.matches());

排除型字符组看起来很简单,不过新手常常会犯一个错误,就是把“在当前位置匹配一个没有列出的字符”理解成“在当前位置不要匹配列出的字符”,两者其实不同的,后者暗示“这里不出现任何字符也可以”。排除型字符组必须匹配一个字符,这点一定要记住。例:

//排除型字符组必须匹配一个字
Stringtext="A8";
Patternp=Pattern.compile("[^0-9][0-9]");
Matcherm=p.matcher(text);
System.out.println(m.matches());//true
text="8";
Matcherm1=p.matcher(text);
System.out.println(m1.matches());//false

除了开方括号 [ 之后的 ^,排除型字符组的用法与普通字符组几乎完全相同,唯一需要改动的是:在排除型字符组中,如果需要表示横线字符 - (而不是用于“-范围表示法”),那么 - 应该紧跟在 ^ 之后; 而在普通字符组中,作为普通字符的横线 - 应该紧跟在开方括号之后

//在排除型字符组中,紧跟在^之后的-不是元字符
Stringtext="-";
Patternp=Pattern.compile("[^-09]");
Matcherm=p.matcher(text);
System.out.println(m.matches());//false
text="8";
Matcherm1=p.matcher(text);
System.out.println(m1.matches());//true

在排除型字符组中,^ 是一个元字符,但只有它紧跟在 [ 之后时才是元字符,如果想表示“这个字符组中可以出现 ^ 字符”,不要让它紧挨着 [ 即可,否则就要转义。如例:

//匹配一个012之外的字符
Stringtext="^";
Patternp=Pattern.compile("[^012]");
Matcherm=p.matcher(text);
System.out.println(m.matches());//true
//匹配4个字符之一:012^
Patternp1=Pattern.compile("[0^12]");
Matcherm1=p1.matcher(text);
System.out.println(m1.matches());//true
//^紧跟在[之后,但已经转义成普通字符,等于上面的表达式,不推荐
Patternp2=Pattern.compile("[\\^012]");
Matcherm2=p2.matcher(text);
System.out.println(m2.matches());//true

字符组简记法

用[0-9],[a-z]等字符组,可以很方便地表示数字字符和小写字母字符。对于这类常用的字符组,正则表达式提供了更简单的记法,这就是字符组简记法(shorthands)。

常见的字符组简记法有 \d\w\s。从表面上看,它们与[...]完全没联系,其实是一致的。其中 \d 等价于 [0-9],其中的d代表“数字(digit)”;\w 等价于 [0-9a-zA-Z_],其中的w代表“单词字符(word)”; \s 等价于 [ \t\r\n\v\f](第一个字符是空格)s表示“空白字符(space)”。下例说明了这几个字符组简记法的典型匹配:

Stringtext="8";
Patternp=Pattern.compile("\\d");
Matcherm=p.matcher(text);
System.out.println(m.matches());//true
text="a";
Patternp1=Pattern.compile("\\w");
Matcherm1=p1.matcher(text);
System.out.println(m1.matches());//true
text="\t";
Patternp2=Pattern.compile("\\s");
Matcherm2=p2.matcher(text);
System.out.println(m2.matches());//true

一般印像中,单词字符似乎只包含大小写字母,但是字符组简记法中的“单词字符”不只有大小写单词,还包含数字字符和下画线_,其中的下画线 _ 尤其值得注意:在进行数据验证时,有可能只容许输入“数字和字母”,有人会人偷懒用 \w 验证,而忽略了 \w 能匹配下画线,所以这种匹配不严格,[0-9a-zA-Z]才是准确的选择。

空白字符”并不难定义,它可以是空格字符,制表符\t,回车符\r,换行符\n等各种“空白”字符,只是不方便展现(因为显示和印刷出来都是空白)。不过这也提醒我们注意,匹配时看到的“空白”可能不是空格字符,因此, \s 才是准确的选择。

字符组简记法可以单独出现,也可以使用在字符组中,比如 [0-9a-zA-Z]也可以写作[\da-zA-Z],所以匹配十六进制字符的字符组可以写成 [\da-fA-F]。字符组简记法也可以用在排除型字符组中,比如 [^0-9]就可以写成[^\d],[^0-9a-zA-Z_]就可以写成 [^\w],代码如下:

Stringtext="8";
//在普通字符组内
Patternp=Pattern.compile("[\\da-zA-Z_]");
Matcherm=p.matcher(text);
System.out.println(m.matches());//true
//在排除型字符组内
Patternp1=Pattern.compile("[^\\w]");
Matcherm1=p1.matcher(text);
System.out.println(m1.matches());//false

相对于 \d,\w和\s 这三个普通字符组简记法,正则表达式也提供了对应排除型字符组的简记法:\D,\W\S ——字母完全一样,只是改为大写。这些简记法匹配的字符互补:\s能匹配的字符,\S一定不能匹配;\w能匹配的字符,\W一定不能匹配;\d能匹配的字符,\D一定不能匹配。例:

//\d和\D
Stringtext="8";
Patternp=Pattern.compile("\\d");
Patternpp=Pattern.compile("\\D");
Matcherm=p.matcher(text);
Matchermm=pp.matcher(text);
System.out.println(m.matches());//true
System.out.println(mm.matches());//false
//\w和\W
text="a";
Patternp1=Pattern.compile("\\w");
Patternpp1=Pattern.compile("\\W");
Matcherm1=p1.matcher(text);
Matchermm1=pp1.matcher(text);
System.out.println(m1.matches());//true
System.out.println(mm1.matches());//false
//\s和\S
text="\t";
Patternp2=Pattern.compile("\\s");
Patternpp2=Pattern.compile("\\S");
Matcherm2=p2.matcher(text);
Matchermm2=pp2.matcher(text);
System.out.println(m2.matches());//true
System.out.println(mm2.matches());//false

妥善利用这种互补的属性,可以得到一些非常巧妙的效果,最简单的应用就是字符组[\s\S]。初看起来,在同一个字符组中并列两个互补的简记法,这种做法有点奇怪,不过仔细想想就会明白, \s和\S组合在一起,匹配的就是“所有的字符”(或者叫“任意字符”)。许多语言中的正则表达式并没有直接提供“任意字符”的表示法,所以 [\s\S\,[\w\W],[\d\D]虽然看起来有点古怪,但确实可以匹配任意字符(注:许多关于正则表达式的文档说,点号(.)能匹配任意字符。但在认情况下,点号其实不能匹配换行符!!!)。

关于字符组简单法,最后需要补充两点:第一如果字符组中出现了字符组简记法,最好不要出现单独的 - ,否则可能引起错误,比如 [\d-a]就很迷惑,在有些语言中,- 会被作为普通字符,而有些语言中,这样写会报错;第二以上说的 \d,\w,\s的匹配规则,都是针对ASCII编码而言,也叫ASCII匹配规则。但是,目前一些语言中的正则表达式已经支持了Unicode字符,那么数字字符,单词字符,空白字符的范围,已经不仅仅限于ASCII编码中的字符(后面再讲)。

字符组运算

有些语言为字符组提供了更强大的功能,比如Java和.Net就提供了字符组运算的功能可以在字符组内进行集合运算,在某些情况下这种功能非常实用。

如果要匹配所有的元音字母,可以用 [aeIoU],但是要匹配所有的辅音字母却没有什么方便的办法,最直接的写法是[b-df-hj-np-tv-z],不但烦琐,而且难理解。其实,从26个字母中“减去”元音字母,剩下的就是辅音字母,如果有办法做个“减法”,就方便多了。Java语言中提供了这样的字符组:[[a-z]&&[^aeIoU]]表示除元音字母之外的所有字符(还包括大写字母,数字和各种符号),两者取交集,就得到“26个英文字母中,除去5个元音字母,剩下的21个辅音字母”。示例:

//字符组运算
Stringtext="p";
Patternp=Pattern.compile("[[a-z]&&[^aeIoU]]");
Matcherm=p.matcher(text);
System.out.println(m.matches());//true

POSIX字符组

前面介绍了常用的字符组,但是在某些文档中,你可能会发现类似 [:digit:],[:lower:]之类的字符组,看起来不难理解(digit就是“数字”,lower就是“小写”)但又很奇怪,它们就是POSIX字符组(POSIX Character Class)。因为某些语言的文档中出现了这些字符组,为避免困惑,这里有必要做个简要介绍。如果只使用常用的编程语言,可以忽略文档中的POSIX字符组,也可以忽略本节;如果想了解POSIX字符组,或者需要在Linux/UNIX下的各种工具中使用正则表达式,最好阅读本节。(注:你书上既然这样说,而我又只在Java中使用,这节的内容我也就过滤了,不写出来了!!!)

原文地址:https://www.jb51.cc/regex/360835.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐