学习正则表达式
什么是正则表达式?
正则表达式是一组由字母和符号组成的特殊文本, 它可以用来从文本中找出满足你想要的格式的句子.
一个正则表达式是在一个主体字符串中从左到右匹配字符串时的一种样式. "Regular expression"这个词比较拗口, 我们常使用缩写的术语"regex"或"regexp". 正则表达式可以从一个基础字符串中根据一定的匹配模式替换文本中的字符串、验证表单、提取字符串等等.
想象你正在写一个应用, 然后你想设定一个用户命名的规则, 让用户名包含字符,数字,下划线和连字符,以及限制字符的个数,好让名字看起来没那么丑. 我们使用以下正则表达式来验证一个用户名:
以上的正则表达式可以接受 john_doe
, jo-hn_doe
, john12_as
. 但不匹配Jo
, 因为它包含了大写的字母而且太短了.
1. 基本匹配
正则表达式其实就是在执行搜索时的格式, 它由一些字母和数字组合而成. 例如: 一个正则表达式 the
, 它表示一个规则: 由字母t
开始,接着是h
,再接着是e
.
"the" => The fat cat sat on the mat.
正则表达式123
匹配字符串123
. 它逐个字符的与输入的正则表达式做比较.
正则表达式是大小写敏感的, 所以The
不会匹配the
.
"The" => The fat cat sat on the mat.
2. 元字符
正则表达式主要依赖于元字符. 元字符不代表他们本身的字面意思, 他们都有特殊的含义. 一些元字符写在方括号中的时候有一些特殊的意思. 以下是一些元字符的介绍:
2.1 点运算符 .
.
是元字符中最简单的例子. .
匹配任意单个字符, 但不匹配换行符. 例如, 表达式.ar
匹配一个任意字符后面跟着是a
和r
的字符串.
1 ".ar" => The car parked in the garage.
2.2 字符集
字符集也叫做字符类. 方括号用来指定一个字符集. 在方括号中使用连字符来指定字符集的范围. 在方括号中的字符集不关心顺序. 例如, 表达式[Tt]he
匹配 the
和 The
.
"[Tt]he" => The car parked in the garage.
方括号的句号就表示句号. 表达式 ar[.]
匹配 ar.
字符串
"ar[.]" => A garage is a good place to park a car.
2.2.1 否定字符集
一般来说 ^
表示一个字符串的开头, 但它用在一个方括号的开头的时候, 它表示这个字符集是否定的. 例如, 表达式[^c]ar
匹配一个后面跟着ar
的除了c
的任意字符.
"[^c]ar" => The car parked in the garage.
2.3 重复次数
后面跟着元字符 +
, *
or ?
的, 用来指定匹配子模式的次数. 这些元字符在不同的情况下有着不同的意思.
2.3.1 *
号
*
号匹配 在*
之前的字符出现大于等于0
次. 例如, 表达式 a*
匹配以0或更多个a开头的字符, 因为有0个这个条件, 其实也就匹配了所有的字符. 表达式[a-z]*
匹配一个行中所有以小写字母开头的字符串.
"[a-z]*" => The car parked in the garage #21.
*
字符和.
字符搭配可以匹配所有的字符.*
. *
和表示匹配空格的符号\s
连起来用, 如表达式\s*cat\s*
匹配0或更多个空格开头和0或更多个空格结尾的cat字符串.
"\s*cat\s*" => The fat cat sat on the concatenation.
2.3.2 +
号
+
号匹配+
号之前的字符出现 >=1 次个字符. 例如表达式c.+t
匹配以首字母c
开头以t
结尾,中间跟着任意个字符的字符串.
"c.+t" => The fat cat sat on the mat.
2.3.3 ?
号
在正则表达式中元字符 ?
标记在符号前面的字符为可选, 即出现 0 或 1 次. 例如, 表达式 [T]?he
匹配字符串 he
和 The
.
"[T]he" => The car is parked in the garage.
"[T]?he" => The car is parked in the garage.
2.4 {}
号
在正则表达式中 {}
是一个量词, 常用来一个或一组字符可以重复出现的次数. 例如, 表达式 [0-9]{2,3}
匹配 2~3 位 0~9 的数字.
"[0-9]{2,3}" => The number was 9.9997 but we rounded it off to 10.0.
我们可以省略第二个参数. 例如, [0-9]{2,}
匹配至少两位 0~9 的数字.
如果逗号也省略掉则表示重复固定的次数. 例如, [0-9]{3}
匹配3位数字
"[0-9]{2,}" => The number was 9.9997 but we rounded it off to 10.0.
"[0-9]{3}" => The number was 9.9997 but we rounded it off to 10.0.
2.5 (...)
特征标群
特征标群是一组写在 (...)
中的子模式. 例如之前说的 {}
是用来表示前面一个字符出现指定次数. 但如果在 {}
前加入特征标群则表示整个标群内的字符重复 N 次. 例如, 表达式 (ab)*
匹配连续出现 0 或更多个 ab
.
我们还可以在 ()
中用或字符 |
表示或. 例如, (c|g|p)ar
匹配 car
或 gar
或 par
.
"(c|g|p)ar" => The car is parked in the garage.
2.6 |
或运算符
或运算符就表示或, 用作判断条件.
例如 (T|t)he|car
匹配 (T|t)he
或 car
.
"(T|t)he|car" => The car is parked in the garage.
2.7 转码特殊字符
反斜线 \
在表达式中用于转码紧跟其后的字符. 用于指定 { } [ ] / \ + * . $ ^ | ?
这些特殊字符. 如果想要匹配这些特殊字符则要在其前面加上反斜线 \
.
例如 .
是用来匹配除换行符外的所有字符的. 如果想要匹配句子中的 .
则要写成 \.
以下这个例子 \.?
是选择性匹配.
"(f|c|m)at\.?" => The fat cat sat on the mat.
2.8 锚点
在正则表达式中, 想要匹配指定开头或结尾的字符串就要使用到锚点. ^
指定开头, $
指定结尾.
2.8.1 ^
号
^
用来检查匹配的字符串是否在所匹配字符串的开头.
例如, 在 abc
中使用表达式 ^a
会得到结果 a
. 但如果使用 ^b
将匹配不到任何结果. 因为在字符串 abc
中并不是以 b
开头.
例如, ^(T|t)he
匹配以 The
或 the
开头的字符串.
"(T|t)he" => The car is parked in the garage.
"^(T|t)he" => The car is parked in the garage.
2.8.2 $
号
同理于 ^
号, $
号用来匹配字符是否是最后一个.
例如, (at\.)$
匹配以 at.
结尾的字符串.
"(at\.)" => The fat cat. sat. on the mat.
"(at\.)$" => The fat cat. sat. on the mat.
3. 简写字符集
正则表达式提供一些常用的字符集简写. 如下:
4. 前后关联约束(前后预查)
前置约束和后置约束都属于非捕获簇(用于匹配不在匹配列表中的格式). 前置约束用于判断所匹配的格式是否在另一个确定的格式之后.
例如, 我们想要获得所有跟在 $
符号后的数字, 我们可以使用正向向后约束 (?<=\$)[0-9\.]*
. 这个表达式匹配 $
开头, 之后跟着 0,1,2,3,4,5,6,7,8,9,.
这些字符可以出现大于等于 0 次.
前后关联约束如下:
4.1 ?=...
前置约束(存在)
?=...
前置约束(存在), 表示第一部分表达式必须跟在 ?=...
定义的表达式之后.
返回结果只满足第一部分表达式. 定义一个前置约束(存在)要使用 ()
. 在括号内部使用一个问号和等号: (?=...)
.
前置约束的内容写在括号中的等号后面. 例如, 表达式 (T|t)he(?=\sfat)
匹配 The
和 the
, 在括号中我们又定义了前置约束(存在) (?=\sfat)
,即 The
和 the
后面紧跟着 (空格)fat
.
"(T|t)he(?=\sfat)" => The fat cat sat on the mat.
4.2 ?!...
前置约束-排除
前置约束-排除 ?!
用于筛选所有匹配结果, 筛选条件为 其后不跟随着定义的格式 前置约束-排除
定义和 前置约束(存在)
一样, 区别就是 =
替换成 !
也就是 (?!...)
.
表达式 (T|t)he(?!\sfat)
匹配 The
和 the
, 且其后不跟着 (空格)fat
.
"(T|t)he(?!\sfat)" => The fat cat sat on the mat.
4.3 ?<= ...
后置约束-存在
后置约束-存在 记作(?<=...)
用于筛选所有匹配结果, 筛选条件为 其前跟随着定义的格式. 例如, 表达式 (?<=(T|t)he\s)(fat|mat)
匹配 fat
和 mat
, 且其前跟着 The
或 the
.
"(?<=(T|t)he\s)(fat|mat)" => The fat cat sat on the mat.
4.4 ?<!...
后置约束-排除
后置约束-排除 记作 (?<!...)
用于筛选所有匹配结果, 筛选条件为 其前不跟着定义的格式. 例如, 表达式 (?<!(T|t)he\s)(cat)
匹配 cat
, 且其前不跟着 The
或 the
.
"(?<!(T|t)he\s)(cat)" => The cat sat on cat.
5. 标志
标志也叫修饰语, 因为它可以用来修改表达式的搜索结果. 这些标志可以任意的组合使用, 它也是整个正则表达式的一部分.
5.1 忽略大小写 (Case Insensitive)
修饰语 i
用于忽略大小写. 例如, 表达式 /The/gi
表示在全局搜索 The
, 在后面的 i
将其条件修改为忽略大小写, 则变成搜索the
和 The
, g
表示全局搜索.
"The" => The fat cat sat on the mat.
"/The/gi" => The fat cat sat on the mat.
5.2 全局搜索 (Global search)
修饰符 g
常用语执行一个全局搜索匹配, 即(不仅仅返回第一个匹配的, 而是返回全部). 例如, 表达式 /.(at)/g
表示搜索 任意字符(除了换行) + at
, 并返回全部结果.
"/.(at)/" => The fat cat sat on the mat.
"/.(at)/g" => The fat cat sat on the mat.
5.3 多行修饰符 (Multiline)
多行修饰符 m
常用语执行一个多行匹配.
像之前介绍的 (^,$)
用于检查格式是否是在待检测字符串的开头或结尾. 但我们如果想要它在每行的开头和结尾生效, 我们需要用到多行修饰符 m
.
例如, 表达式 /at(.)?$/gm
表示在待检测字符串每行的末尾搜索 at
后跟一个或多个 .
的字符串, 并返回全部结果.
"/.at(.)?$/" => The fat cat sat on the mat.
"/.at(.)?$/gm" => The fat cat sat on the mat.
实例应用------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2.用途:
字符串匹配(字符匹配)
字符串查找
字符串替换
字符串分割
例如:
- 从网页中揪出email地址
- IP地址是否正确
- 从网页中揪出链接
3.java中处理正则表达式的类:
- java.lang.String
- java.util.regex.Pattern:模式类:字符串要被匹配的这么一个模式,该模式本身已经被编译过,使用的话效率要高很多。
- java.util.regex.Matcher:匹配类:这个模式匹配某个字符串所产生的结果,这个结果可能会有很多个。
4:下面通过一个小程序简单介绍一下正则表达式
1 import java.util.regex.Matcher; 2 import java.util.regex.Pattern; 3 4 public class Test { 5 6 public static void main(String[] args) { 7 //matches()判断字符串是否匹配某个表达式,"."表示任何一个字符 8 p("abc".matches("...")); 9 //将字符串"a2389a"中的数字用*替换,\d 表示“0--9”数字 10 p("a2389a".replaceAll("\\d", "*")); 11 //将任何是a--z的字符串长度为3的字符串进行编译,这样可以加快匹配速度 12 Pattern p = Pattern.compile("[a-z]{3}"); 13 //进行匹配,并将匹配结果放在Matcher对象中 14 Matcher m = p.matcher("abc"); 15 p(m.matches()); 16 //上面的三行代码可以用下面一行代码代替 17 p("abc".matches("[a-z]{3}")); 18 } 19 20 public static void p(Object o){ 21 System.out.println(o); 22 } 23 }
下面是打印结果
1 true 2 a****a 3 true 4 true
现在通过一些实验来说明正则表达式的匹配规则,这儿是Greedy方式
. 任何字符
a? a一次或一次也没有
a* a零次或多次
a+ a一次或多次
a{n}? a恰好 n 次
a{n,}? a至少n次
a{n,m}? a至少n次,但是不超过m次
1 //初步认识. * + ? 2 p("a".matches("."));//true 3 p("aa".matches("aa"));//true 4 p("aaaa".matches("a*"));//true 5 p("aaaa".matches("a+"));//true 6 p("".matches("a*"));//true 7 p("aaaa".matches("a?"));//false 8 p("".matches("a?"));//true 9 p("a".matches("a?"));//true 10 p("1232435463685899".matches("\\d{3,100}"));//true 11 p("192.168.0.aaa".matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"));//false 12 p("192".matches("[0-2][0-9][0-9]"));//true
[abc] a、b 或 c(简单类)
[^abc] 任何字符,除了 a、b 或 c(否定)
[a-zA-Z] a 到 z 或 A 到 Z,两头的字母包括在内(范围)
[a-d[m-p]] a 到 d 或 m 到 p:[a-dm-p](并集)
[a-z&&[def]] d、e 或 f(交集)
[a-z&&[^bc]] a 到 z,除了 b 和 c:[ad-z](减去)
[a-z&&[^m-p]] a 到 z,而非 m 到 p:[a-lq-z](减去)
1 //范围 2 p("a".matches("[abc]"));//true 3 p("a".matches("[^abc]"));//false 4 p("A".matches("[a-zA-Z]"));//true 5 p("A".matches("[a-z]|[A-Z]"));//true 6 p("A".matches("[a-z[A-Z]]"));//true 7 p("R".matches("[A-Z&&[RFG]]"));//true
\d 数字:[0-9]
\D 非数字: [^0-9]
\s 空白字符:[ \t\n\x0B\f\r]
\S 非空白字符:[^\s]
\w 单词字符:[a-zA-Z_0-9]
\W 非单词字符:[^\w]
1 //认识\s \w \d \ 2 p("\n\r\t".matches("\\s(4)"));//false 3 p(" ".matches("\\S"));//false 4 p("a_8 ".matches("\\w(3)"));//false 5 p("abc888&^%".matches("[a-z]{1,3}\\d+[&^#%]+"));//true 6 p("\\".matches("\\\\"));//true
边界匹配器
^ 行的开头
$ 行的结尾
\b 单词边界
\B 非单词边界
\A 输入的开头
\G 上一个匹配的结尾
\Z 输入的结尾,仅用于最后的结束符(如果有的话)
\z 输入的结尾
1 //边界匹配 2 p("hello sir".matches("^h.*"));//true 3 p("hello sir".matches(".*ir$"));//true 4 p("hello sir".matches("^h[a-z]{1,3}o\\b.*"));//true 5 p("hellosir".matches("^h[a-z]{1,3}o\\b.*"));//false 6 //空白行:一个或多个(空白并且非换行符)开头,并以换行符结尾 7 p(" \n".matches("^[\\s&&[^\\n]]*\\n$"));//true
方法解析
matches():匹配整个字符串
find():匹配子字符串
lookingAt():永远从整个字符串的开头开始匹配
1 //email 2 p("asdsfdfagf@adsdsfd.com".matches("[\\w[.-]]+@[\\w[.-]]+\\.[\\w]+"));//true 3 4 //matches() find() lookingAt() 5 Pattern p = Pattern.compile("\\d{3,5}"); 6 Matcher m = p.matcher("123-34345-234-00"); 7 8 //将整个"123-34345-234-00"用正则表达式引擎查找匹配,当到第一个"-"不匹配了,就停止, 9 //但不会将不匹配的"-"吐出来 10 p(m.matches()); 11 //将不匹配的"-"吐出来 12 m.reset(); 13 14 //1:当前面有p(m.matches());查找子字符串从"...34345-234-00"开始 15 //将会是第1,2两个查到"34345"和"234" 后面2个查不到为false 16 //2:当前面有p(m.matches());和m.reset();查找子字符串从"123-34345-234-00"开始 17 //将为true,true,true,false 18 p(m.find()); 19 p(m.start()+"---"+m.end()); 20 p(m.find()); 21 p(m.start()+"---"+m.end()); 22 p(m.find()); 23 p(m.start()+"---"+m.end()); 24 p(m.find()); 25 //要是没找到就会报异常java.lang.IllegalStateException 26 //p(m.start()+"---"+m.end()); 27 28 p(m.lookingAt()); 29 p(m.lookingAt()); 30 p(m.lookingAt()); 31 p(m.lookingAt());
字符串替换:下面这种方法对于字符串替换非常灵活
1 //字符串替换 2 //Pattern.CASE_INSENSITIVE大小写不敏感 3 Pattern p = Pattern.compile("java",Pattern.CASE_INSENSITIVE); 4 Matcher m = p.matcher("java Java jAva ILoveJavA youHateJAVA adsdsfd"); 5 //存放字符串 6 StringBuffer buf = new StringBuffer(); 7 //计数奇偶数 8 int i = 0; 9 while(m.find()){ 10 i++; 11 if(i%2 == 0){ 12 m.appendReplacement(buf, "java"); 13 }else{ 14 m.appendReplacement(buf, "JAVA"); 15 } 16 } 17 //不加这句话,字符串adsdsfd将会被遗弃 18 m.appendTail(buf); 19 p(buf);
结果打印:
1 JAVA java JAVA ILovejava youHateJAVA adsdsfd
分组
1 //group分组,用()分组 2 Pattern p = Pattern.compile("(\\d{3,5})([a-z]{2})"); 3 String s = "123aa-34345bb-234cc-00"; 4 Matcher m = p.matcher(s); 5 p(m.groupCount());//2组 6 while(m.find()){ 7 p(m.group());//数字字母都有 8 //p(m.group(1));//只有数字 9 //p(m.group(2));//只有字母 10 }
抓取网页中的Email地址
利用正则表达式匹配网页中的文本
[\\w[.-]]+@[\\w[.-]]+\\.[\\w]+
将网页内容分割提取
1 import java.io.BufferedReader; 2 import java.io.FileNotFoundException; 3 import java.io.FileReader; 4 import java.io.IOException; 5 import java.util.regex.Matcher; 6 import java.util.regex.Pattern; 7 8 9 public class EmailSpider { 10 11 public static void main(String[] args) { 12 try { 13 BufferedReader br = new BufferedReader(new FileReader("C:\\emailSpider.html")); 14 String line = ""; 15 while((line=br.readLine()) != null) { 16 parse(line); 17 } 18 } catch (FileNotFoundException e) { 19 e.printStackTrace(); 20 } catch (IOException e) { 21 e.printStackTrace(); 22 } 23 } 24 25 private static void parse(String line) { 26 Pattern p = Pattern.compile("[\\w[.-]]+@[\\w[.-]]+\\.[\\w]+"); 27 Matcher m = p.matcher(line); 28 while(m.find()) { 29 System.out.println(m.group()); 30 } 31 } 32 33 }
打印结果:
1 867124664@qq.com 2 260678675@QQ.com 3 806208721@qq.com 4 hr_1985@163.com 5 32575987@qq.com 6 qingchen0501@126.com 7 yingyihanxin@foxmail.com 8 1170382650@qq.com 9 1170382650@qq.com 10 yingyihanxin@foxmail.com 11 qingchen0501@126.com 12 32575987@qq.com 13 hr_1985@163.com
现在你找到这么多邮箱地址,用上JavaMail的知识,你可以群发垃圾邮件了,呵呵!!!
二:代码统计
1 import java.io.BufferedReader; 2 import java.io.File; 3 import java.io.FileNotFoundException; 4 import java.io.FileReader; 5 import java.io.IOException; 6 7 public class CodeCounter { 8 9 static long normalLines = 0;//正常代码行 10 static long commentLines = 0;//注释行 11 static long whiteLines = 0;//空白行 12 13 public static void main(String[] args) { 14 //找到某个文件夹,该文件夹下面在没有文件夹,这里没有写递归处理不在同一文件夹的文件 15 File f = new File("E:\\Workspaces\\eclipse\\Application\\JavaMailTest\\src\\com\\java\\mail"); 16 File[] codeFiles = f.listFiles(); 17 for(File child : codeFiles){ 18 //只统计java文件 19 if(child.getName().matches(".*\\.java$")) { 20 parse(child); 21 } 22 } 23 24 System.out.println("normalLines:" + normalLines); 25 System.out.println("commentLines:" + commentLines); 26 System.out.println("whiteLines:" + whiteLines); 27 28 } 29 30 private static void parse(File f) { 31 BufferedReader br = null; 32 //表示是否为注释开始 33 boolean comment = false; 34 try { 35 br = new BufferedReader(new FileReader(f)); 36 String line = ""; 37 while((line = br.readLine()) != null) { 38 //去掉注释符/*前面可能出现的空白 39 line = line.trim(); 40 //空行 因为readLine()将字符串取出来时,已经去掉了换行符\n 41 //所以不是"^[\\s&&[^\\n]]*\\n$" 42 if(line.matches("^[\\s&&[^\\n]]*$")) { 43 whiteLines ++; 44 } else if (line.startsWith("/*") && !line.endsWith("*/")) { 45 //统计多行/*****/ 46 commentLines ++; 47 comment = true; 48 } else if (line.startsWith("/*") && line.endsWith("*/")) { 49 //统计一行/**/ 50 commentLines ++; 51 } else if (true == comment) { 52 //统计*/ 53 commentLines ++; 54 if(line.endsWith("*/")) { 55 comment = false; 56 } 57 } else if (line.startsWith("//")) { 58 commentLines ++; 59 } else { 60 normalLines ++; 61 } 62 } 63 } catch (FileNotFoundException e) { 64 e.printStackTrace(); 65 } catch (IOException e) { 66 e.printStackTrace(); 67 } finally { 68 if(br != null) { 69 try { 70 br.close(); 71 br = null; 72 } catch (IOException e) { 73 e.printStackTrace(); 74 } 75 } 76 } 77 } 78 79 }
学问:纸上得来终觉浅,绝知此事要躬行
为事:工欲善其事,必先利其器。
态度:道阻且长,行则将至;行而不辍,未来可期
.....................................................................
------- 桃之夭夭,灼灼其华。之子于归,宜其室家。 ---------------
------- 桃之夭夭,有蕡其实。之子于归,宜其家室。 ---------------
------- 桃之夭夭,其叶蓁蓁。之子于归,宜其家人。 ---------------
=====================================================================
* 博客文章部分截图及内容来自于学习的书本及相应培训课程以及网络其他博客,仅做学习讨论之用,不做商业用途。
* 如有侵权,马上联系我,我立马删除对应链接。 * @author Alan -liu * @Email no008@foxmail.com
转载请标注出处! ✧*꧁一品堂.技术学习笔记꧂*✧. ---> https://www.cnblogs.com/ios9/