Java基础之正则表达式

Java 正则表达式

意义

  • 正则表达式是对字符串操作的一种逻辑公式
    • 事先定义好的特定字符、及特定字符的组合组成规则字符串
    • 表达对字符串的一种过滤逻辑

给定一个正则表达式和另一个字符串可以实现:

  • 字符串是否符合正则表达式的过滤逻辑(“匹配”)

  • 通过正则表达式从字符串中获取特定部分

特点

  • 灵活性、逻辑性和功能性非常强

  • 可以迅速地用极简单的方式达到字符串的复杂控制

  • 对于刚接触的人来说,比较晦涩难懂

  • 正则表达式写好后,没有错对之分,返回结果只是true和false

  • 定义了字符串的模式,可以用来搜索、编辑或处理文本

    • 并不局限某种语言,但在不同语言有细微差别

实例:一个字符串就是一个简单的正则表达式

  • Hello World 正则表达式匹配"Hello World"字符串
  • . (点号)也是一个正则表达式,匹配任何一个字符:“a” 或 “1”
正则表达式描述
this is text匹配字符串"this is text"
this\s+is\s+text注意\s+可以匹配多个空格,之后匹配is字符串,再之后\s+匹配多个空格再跟上text字符串;例:this is text
^\d+(\.\d+)?^ 定义了以什么开始
\d+ 匹配一个或多个数字
? 设置括号内的选项是可选的
\. 匹配 “.”
可以匹配的实例:“5”, “1.5” 和 “2.21”

String

string 类型字符串可以转为其他任何类型,所以用户输入内容一般用 String 接收

  • 转为其他类型数据为确保正确性需要复杂验证
    • 正则表达式就可以对字符串内容进行拆分、验证
    • 通常用来进行通用内容格式验证,或内容的定向读取
//String 中可以直接使用正则表达式 
	//matcher(): 判断是否相同,返回布尔值
String  s = "456137900";
System.out.println(s.matcher("\\d+"));  //结果为true,匹配所有数字
	//split(): 分割字符串
String content = "hello,world!--你好啊?世界";
        String[] split = content.split(",|!--|?");
        int i = 0;
        for (String s1 : split){
            System.out.println(++i + ": " + s1);
        }
运行结果:
1: hello
2: world
3: 你好啊
4: 世界
	//哑巴去重可写为下列代码
String a = "我我...要要要....学学..java";
a = a.replaceAll("\\.*", ""); //将所有的。 换成""
a = a.replaceAll("(.)\\1*", "$1");  //将所有重复内容换为第一个

regex包

Pattern类
  • Pattern对象是正则表达式的编译表示
    • 没有公共构造方法,调用其公共静态方法返回一个Pattern对象
      • 该方法接受一个正则表达式作为它第一个参数
      • 提供正则表达式编译处理
        • public static Patterm compile(String regex)
      • 字符串拆分操作
        • public String[ split(CharSequence input)
Matcher类
用法
  • 实现正则匹配的处理类

  • Pattern类实例化

    • public Matcher matcher(CharSequence input)
  • 获取Matcher对象之后就可以进行该类方法进行正则操作

    • 正则匹配:public boolean matchers()

      • String connect = "I am noob" + "from runoob.com";			// 字符串内容
        String pattern = ".*runoob.*";								// 定义正则表达时
        System.out.println(Pattern.matches(pattern, connect));		// 匹配内容中是否有符合要求的内容
        
    • 字符串替换:public String replaceAll(String replacement)

  • 以拆分,替换,匹配三种操作用不到 java.util.regex 开发包,只依靠 String 类可以实现

    • 但 mather 类里面提供有一种分组的功能,而这种分组的功能是 String 不具备的
捕获组
用法

捕获组是把多个字符当作一个单独单元进行处理的方法,通过对括号内字符分组来创建

  • 例如:正则表达式(dog)创建了单一分组,包含"d",“o”,“g”

捕获组通过自左至右计算其开括号来编号,依次排序(从1开始)

  • 例如:表达式((A)(B(C)))

    • 有四个组((A)(B(C)))(A)(B(C))(C)

      • 用括号组成较复杂的匹配模式,一个括号部分看作一组(一个子表达式)
    • 正则表达式中分组(子表达式)匹配到的内容,保存到内存中以数字编号命名或显示命名的组里,方便后面引用

      • 从左向右,以分组的左括号为标志,依次排序(从1开始)

        • 组 0 代表整个表达式
      • 显示命名

        String reg = "(?i)(?<g>Bi)";  
        //	(?i)(要筛选的字符): 忽略大小写
        //	(?<name>正则式): 给分组命名
        
    • 调用matcher对象的groupCount方法来查看表达式分组数量

      • 返回 int 值表示 matcher 对象当前有多个捕获组
    • 特殊的组(group(0))代表整个表达式,不在返回值内

  • public class RegexMatches{
        public static void main( String[] args ){
          // 按指定模式在字符串查找
          String line = "This order was placed for QT3000! OK?";
          String pattern = "(\\D*)(\\d+)(.*)";      
          Pattern r = Pattern.compile(pattern);					// 创建 Pattern 对象    
          Matcher m = r.matcher(line);							// 创建 matcher 对象
          if (m.find( )) {										// 找到匹配内容
             System.out.println("全部 : " + m.group(0) );		  // 全部内容
             System.out.println("\\D* : " + m.group(1) );		// 第一组 \\D* 非数字 0个 ~ 无穷 
             System.out.println("\\d+ : " + m.group(2) );		// 第二组 \\d+ 数字 1个 ~ 无穷
             System.out.println(".*  : " + m.group(3) ); 		// 第三组 .* 任意内容到最后
          } else {
             System.out.println("NO MATCH");
          }
       }
    }
    /**
    运行结果:
    全部 :This order was placed for QT3000! OK?
    \\D* : This order was placed for QT
    \\d+ : 3000
    .*  : ! OK?
    */
    
URL
  • (?<name>正则式): 给分组命名
    • 可以通过分组名获取分组内容
//要查找的内容
String s = "https://www.bilibili.com/video/BV1fh411y7R8?p=894&spm_id_from=pageDriver";
//正则表达式
String url = "^((http|https)://)?([\\w-]+\\.)+[\\w-]+(\\/[\\w-?$&%#=_/.]*)?$";
	/*
	^((http|https)://)?  		匹配  https://
	([\\w-]+\\.)+		 		匹配	www.bilibili.
	[\\w-]+			 	 		匹配	com
	(\\/[\\w-?$&%#=_/.]*)?$  	匹配	/video/BV1fh411y7R8?p=894&spm_id_from=pageDriver
	*/
/*
String a = "BIBILIlibili";
String reg = "(?i)(?<g>Bi)";  
	//	(?i)(要筛选的字符): 忽略大小写
    //	(?<name>正则式): 给分组命名
while(matcher.find())  						//必须有判断是否有捕获组,否则报异常
System.out.println(matcher.group("g")); 	//用组名获取捕获组
*/
//创建模板对象,将正则表达式传入
Pattern compile = Pattern.compile(url);
//创建匹配器
Matcher matcher = compile.matcher(s);
//输出有分组数量
System.out.println(matcher.groupCount());
//判断有没有捕获组(按括号分组),必须判断,否则报异常
while (matcher.find()) {
    //遍历输出所有捕获组
    for (int i = 0; i <= matcher.groupCount(); i++) {
        //matcher.group(0): 表示匹配到的所有内容
        //mathcer.grou(i): i != 0 表示匹配到的group(0)中的子串(括号中匹配到的内容)
        System.out.println(matcher.group(i));
    }
}
/**
运行结果:
4				//捕获组数量(即括号数量)
https://www.bilibili.com/video/BV1fh411y7R8?p=894&spm_id_from=pageDriver	//全部
https://		//第一组
https			//第二组
bilibili.		//第三组
/video/BV1fh411y7R8?p=894&spm_id_from=pageDriver		//第四组
*/
反向引用

分组内容被捕获后可在这个括号之后使用,称为反向引用

  • 可以是在正则表达式内部,内部反向引用:\\分组号

    String reg = "(abds)(?<g>Bi)\\1\\2";  //分为两组,第二组命名为 g
    	// \\1: 反向引用第一组,即(abds)(?<g>Bi)(abds)
    	// \\2: 反向引用第二组,不能使用命名引用
    	// 整个表达式等同于: (abds)(?<g>Bi)(abds)(Bi)
    
  • 也可用在外部,外部反向引用:$分组号

  •  //结巴去重
    String a = "我我...要要要....学学..java";
    //找出所有的.
    String reg = "\\.*";
    //捕获所有的.
    Matcher matcher = Pattern.compile(reg).matcher(a);
    //将所有的.替换为空
    a = matcher.replaceAll("");
    System.out.println(a);
    //表达式找到所有有重复的字段,使用内部反向引用得到有重复的内容
    reg = "(.)\\1*";
    //外部反向引用将捕获的所有重复内容替换为第一组的内容(仅一个字)
    a = Pattern.compile(reg).matcher(a).replaceAll("$1");
    System.out.println(a);
    /**
    运行结果:
    我我要要要学学java
    我要学java
    */
    
Matcher类方法
索引方法
方法说明
public int start()返回以前匹配的初始索引
public int start(int group)返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引
public int end()返回最后匹配字符之后的偏移量。
public int end(int group)返回在以前的匹配操作期间,由给定组所捕获子序列的最后字符之后的偏移量。
  • start 和 end
private static final String A = "\\bcat\\b";				// 匹配 c 开头 t 结尾的 cat
private static final String B="cat cat cat cattie";
public static void main(String[] args){ 
    Pattern p = Pattern.compile(A); 
    Matcher m = p.matcher(B);		//获取matcher对象  
    int count = 0;  
    while (m.find()){  
        count++;      
        System.out.println("Match number: " + count);   
        System.out.println("start():" + m.start());	  
        System.out.println("end():" + m.end()); 
    }
}
运行结果:
Match number: 1
start():0
end():3
Match number: 2
start():4
end():7
Match number: 3
start():8
end():11
// 使用单词边界,以确保字母 "c" "a" "t" 并非仅是一个较长的词的子串;
// Start 方法返回给定组所捕获的子序列的初始索引,end 方法返回最后一个匹配字符的索引加 1
查找方法
方法说明
public boolean lookingAt()尝试将从区域开头开始的输入序列与该模式匹配
public boolean find()尝试查找与该模式匹配的输入序列的下一个子序列
public boolean find(int start)重置此匹配器,然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列
public boolean matches()尝试将整个区域与模式匹配
  • matches 和lookingAt

    • matches 和 lookingAt 方法都用来尝试匹配一个输入序列模式,经常在输入字符串的开始使用

      • matches 要求整个序列都匹配

      • lookingAt 不要求,但 lookingAt 方法需要从第一个字符开始匹配

private static final String A = "foo";
private static final String B="fooooooooooooooooo";
private static final String C="ooooofoooooooooooo";
private static Pattern pattern;
private static Matcher matcher1;
private static Matcher matcher2;
public static void main( String[] args ){  
    pattern = Pattern.compile(A); 
    matcher1 = pattern.matcher(B); 
    matcher2 = pattern.matcher(C);  
    System.out.println("Current A is: "+ A);  
    System.out.println("Current B is: "+ B);
    System.out.println("Current C is: "+ C);    
    System.out.println("lookingAt(): "+matcher1.lookingAt());   
    System.out.println("matches(): "+matcher1.matches());   
    System.out.println("lookingAt(): "+matcher2.lookingAt());
}
运行结果:
Current A is: foo
Current B is: fooooooooooooooooo
Current C is: ooooofoooooooooooo
lookingAt(): true
matches(): false
lookingAt(): false
替换方法
方法说明
public Matcher appendReplacement (StringBuffer sb, String replacement)实现非终端添加和替换步骤
public StringBuffer appendTail (StringBuffer sb)实现终端添加和替换步骤
public String replaceAll(String replacement)替换模式与给定替换字符串相匹配的输入序列的每个子序列
public String replaceFirst(String replacement)替换模式与给定替换字符串匹配的输入序列的第一个子序列
public static String quoteReplacement (String s)返回指定字符串的字面替换字符串。这个方法返回一个字符串,就像传递给 Matcher类 的appendReplacement 方法一个字面字符串一样工作
  1. replaceFirst 和 replaceAll

    • replaceFirst 和 repalceAll 方法用来替换匹配正则表达式的文本

      • replaceFirst 替换首次匹配

      • replaceAll 替换所有匹配

    • private static String A = "dog";
      private static String B = "The dog says meow. " + "All dogs say meow.";
      private static String REPLACE = "cat"; 
      public static void main(String[] args) {
          Pattern p = Pattern.compile(A);  
          Matcher m = p.matcher(B);//得到matcher对象 
          B = m.replaceAll(REPLACE);  
          System.out.println(B);  
          Pattern p1 = Pattern.compile(REPLACE); 
          Matcher m1 = p1.matcher(B);//得到matcher对象 
          B = m1.replaceFirst(A);  
          System.out.println(B);
      }
      运行结果:
      The cat says meow. All cats say meow.
      The dog says meow. All cats say meow.
      
  2. appendReplacement 和 appendTail

    • appendReplacement(StringBuffer sb, String replacement) 方法
      • sb 是一个 StringBuffer
      • replaceContext 是待替换的字符串
      • 把匹配到的内容替换为 replaceContext
        • 并把从上次替换的位置到这次替换位置之间的字符串拿到
        • 加上本次替换后的结果一起追加到 StringBuffer
  • appendTail :把最后一次匹配到内容之后的字符串追加到 StringBuffer

  • private static String A = "a*b";  						//匹配 0 ~ 无穷个 a 以 一个b 结尾
    private static String B = "aabfooaabfooabfoobkkk";
    private static String REPLACE = "-";
    public static void main(String[] args) {
        Pattern p = Pattern.compile(A); 
        Matcher m = p.matcher(B);// 获取 matcher 对象
        StringBuffer sb = new StringBuffer();   
        while(m.find()){    
            m.appendReplacement(sb, REPLACE);   
            System.out.println(sb.toString());  
        }  
        m.appendTail(sb);//添加最后匹配到的内容之后的字符 
        System.out.println(sb.toString());					// -foo-foo-foo-kkk
    }
    运行结果:
    -
    -foo-
    -foo-foo-
    -foo-foo-foo-
    -foo-foo-foo-kkk
    
PatternSyntaxException
  • PatternSyntaxException是一个非强制异常类,表示一个正则表达式模式中的语法错误
方法说明
public String getDescription()获取错误的描述
public int getIndex()获取错误的索引
public String getPattern()获取错误的正则表达式模式
public String getMessage()返回多行字符串,包含语法错误及其索引的描述、错误的正则表达式模式和模式中错误索引的可视化指示

正则语法

  • 其他语言中\\ 表示在正则表达式中插入一个普通的(字面上的)反斜杠

  • Java中 \\表示:要插入一个正则表达式的反斜线,其后的字符具有特殊的意义

    • 其他的语言中一个反斜杠\就足以具有转义的作用
    • 在 Java 中正则表达式需要两个反斜杠才被解析为转义作用
System.out.print("\\");    // 输出为 \
System.out.print("\\\\");  // 输出为 \\
  • Java Language Specification 要求,Java 源代码的字符串中的反斜线被解释为 Unicode 转义或其他字符转义
  • 因此必须使用两个反斜线,表示正则表达式受到保护不被 Java 字节码编译器解释
    • 例如,当解释为正则表达式时,字符串字面值 \b 与单个退格字符匹配,而 “\\b” 与单词边界匹配
    • 字符串字面值\(hello\)是非法的,将导致编译错误;要与字符串 (hello) 匹配,必须使用字符串字面值\\(hello\\)
匹配格式
单个字符匹配
任意字符  //表示由任意字符组成
\\		 //匹配 \
\n		 //匹配换行
\t		 //匹配制表符
单个字符集
  • 可以从里面任选一个字符
[abc] 	//表示可能是字母 a、b、c中的任意一个;
[^abc]	//表示不是由字母 a、b、C中的任意-一个;
[a-zA-Z]	//表示由一个任意字母所组成,不区分大小写;
[0-9]		//表示由一位数字所组成;
单个简化字符集
.		//表示任意的一个字符;
\\d		//等价于 [0-9] 范围;
\\D		//等价于“[^0-9]”范围;
\\s		//匹配任意的一位空格,可能是一个空格、换行、制表符;
\\S		//匹配任意的非空格数据;
\\w		//匹配字母、 数字、下划线, 等价于 [a-zA-Z0-9_]
\\W 	//匹配非字母、数字、下划线,等价于“[^a-zA-Z0-9_]”;
边界匹配
^	//匹配边界开始
$	//匹配边界结束
数量表示

默认情况下只有添加上了数量单位才可以匹配多位字符;

表达式? 	//该正则可以出现 0 次或 1 次;
表达式*	//该正则可以出现 0 次、1 次或多次;
表达式+	//该正则可以出现 1 次或多次;
表达式{n}	//表达式的长度正好为 n 次;
表达式{n, }	//表达式的长度为 n 次或以上
表达式{n,m}	//表达式的长度在 n~m 次;优先匹配多次,贪心匹配
逻辑表达式
  • 可以连接多个正则
表达式 X 表达式 Y 	// X 表达式之后紧跟上 Y 表达式;
表达式 X | 表达式 Y 	//有一个表达式满足即可;
(表达式)			 //为表达式设置-个整体描述,可以为整体描述设置数量单位
正则格式列表
字符说明
\\将下一字符标记为特殊字符、文本、反向引用或八进制转义符
例如: n匹配字符 n;\n 匹配换行符;序列 \\\\ 匹配 \\\\(匹配 (
^匹配输入字符串开始的位置,如果设置了 RegExp 对象的 Multiline 属性,^ 还会与\n\r之后的位置匹配
$匹配输入字符串结束的位置,如果设置了 RegExp 对象的 Multiline 属性,^ 还会与\n\r之后的位置匹配
*零次或多次匹配前面的字符或子表达式,例如:zo* 匹配 z 和 zoo;* 等效于 {0,}
+一次或多次匹配前面的字符或子表达式, 如:zo+与 zo 和 zoo 匹配,但与 z 不匹配;+ 等效于 {1,}
?零次或一次匹配前面的字符或子表达式,例如:do(es)?匹配 do 或 does 中的 do;? 等效于 {0,1}
{n}n 是非负整数。正好匹配 n 次;例如:o{2}与 Bob 中的 o 不匹配,但与 food 中的两个 o 匹配
{n,}n 是非负整数。至少匹配 n 次;例如:o{2,}不匹配 Bob 中的 o ,而匹配 foooood 中的所有 o。o{1,}等效于o+o{0,}等效于o*
{n,m}mn 是非负整数,其中 n <= m:匹配至少 n 次,至多 m 次
例如:o{1,3}匹配 fooooood 中的头三个 o;o{0,1}等效于 o?`;注意:不能将空格插入逗号和数字之间
?当此字符紧随任何其他限定符(*、+、?、{n}、{n,}、{n,m})之后时,匹配模式是非贪心的
"非贪心的"模式匹配搜索到的、尽可能短的字符串;
默认的"贪心的"模式匹配搜索到的、尽可能长的字符串;例如:字符串 oooo 中,o+?只匹配单个 o ,而o+匹配所有 o
.匹配除\r\n之外的任何单个字符
若要匹配包括\r\n在内的任意字符,请使用诸如[\s\S]之类的模式
(pattern)匹配 pattern 并捕获该匹配的子表达式;可以使用 0 ~ 9 属性从结果"匹配"集合中检索捕获的匹配
若要匹配括号字符 ( ),请使用\(或者\)
(?:pattern)匹配 pattern 但不捕获该匹配的子表达式,即是一个非捕获匹配,不存储供以后使用的匹配
这对于用 or 字符 ( | ) 组合模式部件的情况有用;例如:`industr(?:y
(?=pattern)执行正向预测先行搜索的子表达式,该表达式匹配处于匹配 pattern 的字符串的起始点的字符串
是一个非捕获匹配,不能捕获供以后使用的匹配
例如:`Windows (?=95
(?!pattern)执行反向预测先行搜索的子表达式,该表达式匹配不处于匹配 pattern 的字符串的起始点的搜索字符串
非捕获匹配,即不能捕获供以后使用的匹配
例如:`Windows (?!95
x|y匹配 xy;例如:`z
[xyz]字符集,匹配包含的任一字符;例如:[abc]匹配 plain 中的 a
[^xyz]反向字符集,匹配未包含的任何字符;例如:[^abc]匹配 plain 中 p l i n
[a-z]字符范围,匹配指定范围内的任何字符;例如:[a-z]匹配 a 到 z 范围内的任何小写字母
[^a-z]反向范围字符,匹配不在指定的范围内的任何字符;例如:[^a-z]匹配任何不在 a 到 z 范围内的任何字符
\\b匹配一个字边界,即字与空格间的位置;例如:er\\b匹配 never 中的 er ,但不匹 verb 中的 er
\\B非字边界匹配;例如:er\\B匹配 verb 中的 er ,但不匹配 never 中的 e
\\cx匹配 x 指示的控制字符;例如:\\cM 匹配 Control-M 或回车符
x 的值必须在 A-Z 或 a-z 之间;如果不是这样,则假定 c 就是 c 字符本身
\\d数字字符匹配;等效于 [0-9]
\\D非数字字符匹配;等效于 [^0-9]
\\s匹配任何空白字符,包括单个空格、换行、回车、制表符、换页符等;与[ \\n\\r\\t\\v\\f] 等效
\\S匹配任何非空白字符;与 [^\\f\\n\\r\\t\\v ] 等效。
\\f换页符匹配;等效于 \\x0c\\cL
\\n换行符匹配;等效于 \\x0a\\cJ
\\r匹配一个回车符;等效于 \\x0d\\cM
\\t制表符匹配;与 \\x09\\cI 等效
\\v垂直制表符匹配;与 \\x0b\\cK 等效
\\w匹配任何字类字符,包括下划线;与[A-Za-z0-9_]等效
\\W与任何非单词字符匹配;与[^A-Za-z0-9_]等效
\\xn匹配 n,此处的 n 是一个十六进制转义码
十六进制转义码必须正好是两位数长;例如:\\x41匹配 A;\\x041\\x04&1等效。
允许在正则表达式中使用 ASCII 代码
\\num匹配 num,此处的 num 是一个正整数
到捕获匹配的反向引用;例如:(.)\\1匹配两个连续的相同字符
\\n标识一个八进制转义码 或 反向引用;
如果 \\n前面至少有 n 个捕获子表达式,那么 n 是反向引用;
否则,如果 n 是八进制数 (0-7),那么 n 是八进制转义码
\\nm标识一个八进制转义码或反向引用
如果 \\nm 前面至少有 nm 个捕获子表达式,那么 nm 是反向引用
如果 \\nm 前面至少有 n 个捕获,则 n 是反向引用,后面跟有字符 m
如果两种前面的情况都不存在,则 \\nm 匹配八进制值 nm,其中 n 和 m 是八进制数字 (0-7)
\\nmln 是八进制数 (0-3),m 和 l 是八进制数 (0-7) 时,匹配八进制转义码 nml
\\un匹配 n,其中 n 是以四位十六进制数表示的 Unicode 字符。例如,\\u00A9 匹配版权符号 (©)