Java 文本检索神器 "正则表达式"

科技资讯 投稿 26400 0 评论

Java 文本检索神器

Java 文本检索神器 "正则表达式"

每博一文案

在我们短促而又漫长的一生中,我们在苦苦地寻找人生的幸福,可幸福往往又与我们失之交臂,
当我们为此而耗尽宝贵的。青春年华,皱纹也悄悄地爬上了眼角的时候,我们或许才能悄悄懂得生活实际上意味
着什么。
                                     —————— 《平凡的世界》
叶赛宁的诗,不惋惜,不呼唤,我也不啼哭。金黄的落叶堆满我心间。我已经再不是青春少年。
                                     —————— 《平凡的世界》
生活中有那么一种人,你蔑视甚至污辱他,他不仅视为正常,还对你挺佩服;你要是在人格上对他平等相待,
他反而倒小看你!这种人的情况,在伟鲁迅的不朽著作中详尽诠释,这里就不再赘述。
                                     —————— 《平凡的世界》
一个平平常常的日子,细蒙蒙的雨丝夹着一星半点的雪花,正纷纷淋淋地向大地飘洒着。时令已快到惊蛰,
雪当然再也不会存留,往往还没等落地,就已经消失得无影无踪了。黄土高原严寒而漫长的冬天看来
就要过去,但那真正温暖的春天还远远地没有到来。
                                    —————— 《路遥》

@

目录
    Java 文本检索神器 "正则表达式"
    • 每博一文案
    • 1. 正则表达式的概述
      • 1.1 正则表达式的威力
    • 2. Java中正则表达式的相关类
        2.1 Pattern 类
        • 2.1.1 Pattern 类中的一些标识属性
    • 2.2 Matcher 类
        2.2.1 索引方法
    • 2.2.2 检查方法
    • 2.2.3 替换方法
  • 2.3 PatternSyntaxException异常类
  • 2.4 String 类
      2.4.1 java.lang.String 与 java.util.regex.Pattern等效的几个方法
  • 2.4.2 java.lang.String 与 java.util.regex.Matcher 等效的几个方法
  • 3. 正则表达式的语法
      3.1 元字符: (\\ 转义字符
  • 3.2 元字符:字符匹配符
  • 3.3 元字符正则表达式:不区分大小写的二种方式
  • 3.4 元字符:选择匹配符号
  • 3.5 元字符:限定符
  • 3.6 贪婪匹配(Greedy)/ 非贪婪匹配(Lazy)
      3.6.1 贪婪匹配(Greedy)
    • 3.6.2 非贪婪匹配(Lazy)
  • 3.7 元字符:定位符
  • 4. 正则表达式:分组 / 特殊的分组
      4.1 编号分组
  • 4.2 命名分组
  • 4.3 特殊分组 / 不保存子组
  • 4.4 分组中的括号嵌套
  • 5. 正则表达式的实现的原理分析:
      5.1 正则表达式中不含分组的原理分析
  • 5.2 正则表达式中含分组的原理分析
  • 6. 正则表达式:分组,捕获,反向引用
  • 7. 正则表达式:案例练习讲解
  • 8. 正则表达式:优化建议
      8.1 尽量准确表示匹配范围
  • 8.2 提取出公共部分
  • 8.3 出现可能性大的放左边
  • 8.4 只在必要时才使用子组
  • 8.5 警惕嵌套的子组重复
  • 8.6 避免不同分支重复匹配
  • 9. 资料:常用的正则表达式格式
  • 10. 总结
  • 11. 最后:
  • 1. 正则表达式的概述

    我相信,作为一名程序员或者准程序员,你肯定是知道正则表达式的。作为计算机领域最伟大的发明之一,正则表达式简单、强大,它可以极大地提高我们工作中的文本处理效率。现在,各大操作系统、编程语言、文本编辑器都已经支持正则表达式,甚至我还和极客时间的编辑开玩笑说,他们也应该好好学学正则这门手艺。

    Regular Expression,简称 RE。 顾名思义,正则其实就是一种 描述文本内容组成规律的表示方式

    Linux 命令中,它也可以帮助我们轻松地查找或编辑文件的内容,甚至实现整个文件夹中所有文件的内容替换,比如 grep、egrep、sed、awk、vim 等。另外,在各种文本编辑器中,比如 Atom,Sublime TextVS Code 等,在查找或替换的时候也会使用到它。总之,正则是无处不在的,已经渗透到了日常工作的方方面面。

    通过它的功能和分布的广泛你也能看出来,正则是一个非常值得花时间和精力好好学习的基本技能。之前你花几十分钟才能搞定的事情,可能用正则很快就搞定了;之前不能解决的问题,你系统地学习正则后,可能发现也能轻松解决了。

    1.1 正则表达式的威力

    例如01: 下面这段文本是,我们通过爬虫获取到的,下面我们通过正则表达式获取到其中文本的所有英文单词

    Java平台由Java虚拟机(Java Virtual Machine和Java 应用编程接口(Application Programming Interface、简称API构成。Java 应用编程接口为Java应用提供了一个独立于操作系统的标准接口,可分为基本部分和扩展部分。在硬件或操作系统平台上安装一个Java平台之后,Java应用程序就可运行。Java平台已经嵌入了几乎所有的操作系统。这样Java程序可以只编译一次,就可以在各种系统中运行。Java应用编程接口已经从1.1x版发展到1.2版。常用的Java平台基于Java1.8最近版本为Java19。
    
    package blogs.blog12;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest01 {
        public static void main(String[] args {
            // 假定,编写了爬虫,从百度页面得到如下文本:
            String content = "Java平台由Java虚拟机(Java Virtual Machine和Java 应用编程接口(Application Programming Interface、简称API构成。Java 应用编程接口为Java应用提供了一个独立于操作系统的标准接口," +
                    "可分为基本部分和扩展部分。在硬件或操作系统平台上安装一个Java平台之后," +
                    "Java应用程序就可运行。Java平台已经嵌入了几乎所有的操作系统。" +
                    "这样Java程序可以只编译一次,就可以在各种系统中运行。" +
                    "Java应用编程接口已经从1.1x版发展到1.2版。常用的Java平台基于Java1.8," +
                    "最近版本为Java19。";
    
            // 提取文章中所有的英文单词:
            // 传统方法: 使用遍历方式: 代码量大,效率不高:
            // 正则表达式:
    
            // 1. 先创建一个Pattern 对象,模式对象,可以理解成就是一个正则表达式对象
            Pattern pattern = Pattern.compile("[a-zA-Z]+";
    
            // 2. 创建一个匹配器对象
            // 理解:就是 matcher 匹配器,按照所编写的 pattern(模式/样式,到 content 文本中去匹配
            // 找到就返回 true,否则就返回false
            Matcher matcher = pattern.matcher(content;
    
            // 3. 开始循环匹配
            while(matcher.find( {  // 找到返回 true,否则返回false
                // 匹配内容,文本,放到 matcher.group( 当中
                String group = matcher.group(0;
                System.out.println(group;
            }
        }
    }
    
    

    例如02: 提取到上述文本内容中的所有 数字

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest01 {
        public static void main(String[] args {
            // 假定,编写了爬虫,从百度页面得到如下文本:
            String content = "Java平台由Java虚拟机(Java Virtual Machine和Java 应用编程接口(Application Programming Interface、简称API构成。Java 应用编程接口为Java应用提供了一个独立于操作系统的标准接口," +
                    "可分为基本部分和扩展部分。在硬件或操作系统平台上安装一个Java平台之后," +
                    "Java应用程序就可运行。Java平台已经嵌入了几乎所有的操作系统。" +
                    "这样Java程序可以只编译一次,就可以在各种系统中运行。" +
                    "Java应用编程接口已经从1.1x版发展到1.2版。常用的Java平台基于Java1.8," +
                    "最近版本为Java19。";
    
            // 1.创建一个正则表达式对象
            Pattern pattern = Pattern.compile("[\\d]+";
    
            // 2.创建一个匹配器,用于匹配符合正则表达式的字符串
            Matcher matcher = pattern.matcher(content;
    
            // 循环遍历获取符合条件的字符串
            while(matcher.find( { // 一点一点的同文本中遍历匹配是否符合该正则表达式,符合返回true,否则返回false
                String group = matcher.group(0;
                System.out.println(group;
            }
        }
    }
    

    例如03 : 如下是一段从 www.baidu.com 网页中截取的一段源码:通过正则表达式获取到其中的百度热搜信息

    name="oq" value=""><input type="hidden" name="rsv_pq" value="0xb79379dd001c441c"><input type="hidden" name="rsv_t" value="6ab2PWJNZs4OfZhgLwgMJI9gVsivBq7kMA9T8vJRItmCHeBsBD0QsnashhrZ"><input type="hidden" name="rqlang" value="en"></form><div id="m" class="under-searchbox-tips s_lm_hide "><div id="lm-new"></div></div><div id="s-hotsearch-wrapper" class="s-isindex-wrap s-hotsearch-wrapper hide "><div class="s-hotsearch-title"><a class="hot-title" href="https://top.baidu.com/board?platform=pc&sa=pcindex_entry" target="_blank"><div class="title-text c-font-medium c-color-t" aria-label="百度热搜"><i class="c-icon">&#xe687;</i><i class="c-icon arrow">&#xe613;</i></div></a><a id="hotsearch-refresh-btn" class="hot-refresh c-font-normal c-color-gray2"><i class="c-icon refresh-icon">&#xe619;</i><span class="hot-refresh-text">换一换</span></a></div><ul class="s-hotsearch-content" id="hotsearch-content-wrapper"><li class="hotsearch-item odd" data-index="0"><a class="title-content  c-link c-font-medium c-line-clamp1" href="https://www.baidu.com/s?wd=%E4%BA%8C%E5%8D%81%E5%B1%8A%E4%BA%8C%E4%B8%AD%E5%85%A8%E4%BC%9A%E5%BC%80%E5%A7%8B%E4%B8%BE%E8%A1%8C&amp;sa=fyb_n_homepage&amp;rsv_dl=fyb_n_homepage&amp;from=super&amp;cl=3&amp;tn=baidutop10&amp;fr=top1000&amp;rsv_idx=2&amp;hisfilter=1" target="_blank" ><div class="title-content-noindex" style="display: none;"></div><i class="c-icon title-content-top-icon c-color-red c-gap-right-small" style="display: ;">&#xe62e;</i><span class="title-content-index c-index-single c-index-single-hot0" style="display: none;">0</span><span class="title-content-title">二十届二中全会开始举行</span></a><span class="title-content-mark ie-vertical c-text c-gap-left-small "></span></li><li class="hotsearch-item even" data-index="3"><a class="title-content  c-link c-font-medium c-line-clamp1" href="https://www.baidu.com/s?wd=%E7%AB%99%E4%B8%8A%E6%96%B0%E8%B5%B7%E7%82%B9+%E5%A5%8B%E5%8A%9B%E5%BC%80%E6%96%B0%E5%B1%80&amp;sa=fyb_n_homepage&amp;rsv_dl=fyb_n_homepage&amp;from=super&amp;cl=3&amp;tn=baidutop10&amp;fr=top1000&amp;rsv_idx=2&amp;hisfilter=1" target="_blank" ><div class="title-content-noindex" style="display: none;"></div><i class="c-icon title-content-top-icon c-color-red c-gap-right-small" style="display: none;">&#xe62e;</i><span class="title-content-index c-index-single c-index-single-hot3" style="display: ;">3</span><span class="title-content-title">站上新起点 奋力开新局</span></a><span class="title-content-mark ie-vertical c-text c-gap-left-small "></span></li><li class="hotsearch-item odd" data-index="1"><a class="title-content tag-width c-link c-font-medium c-line-clamp1" href="https://www.baidu.com/s?wd=%E7%94%B7%E5%AD%90%E5%9C%A8%E8%87%AA%E5%AE%B6%E9%B1%BC%E5%A1%98%E7%94%B5%E9%B1%BC%E8%A2%AB%E7%BD%9A+%E8%AD%A6%E6%96%B9%E9%81%93%E6%AD%89&amp;sa=fyb_n_homepage&amp;rsv_dl=fyb_n_homepage&amp;from=super&amp;cl=3&amp;tn=baidutop10&amp;fr=top1000&amp;rsv_idx=2&amp;hisfilter=1" target="_blank" ><div class="title-content-noindex" style="display: none;"></div><i class="c-icon title-content-top-icon c-color-red c-gap-right-small" style="display: none;">&#xe62e;</i><span class="title-content-index c-index-single c-index-single-hot1" style="display: ;">1</span><span class="title-content-title">男子在自家鱼塘电鱼被罚 警方道歉</span></a><span class="title-content-mark ie-vertical c-text c-gap-left-small c-text-hot">热</span></li><li class="hotsearch-item even" data-index="4"><a class="title-content  c-link c-font-medium c-line-clamp1" href="https://www.baidu.com/s?wd=%E4%BA%94%E4%B8%80%E5%81%87%E6%9C%9F%E5%8F%AF%E4%BB%A5%E6%8B%BC%E5%87%BA9%E5%A4%A9%E9%95%BF%E5%81%87&amp;sa=fyb_n_homepage&amp;rsv_dl=fyb_n_homepage&amp;from=super&amp;cl=3&amp;tn=baidutop10&amp;fr=top1000&amp;rsv_idx=2&amp;hisfilter=1" target="_blank" ><div class="title-content-noindex" style="display: none;"></div><i class="c-icon title-content-top-icon c-color-red c-gap-right-small" style="display: none;">&#xe62e;</i><span class="title-content-index c-index-single c-index-single-hot4" style="display: ;">4</span><span class="title-content-title">五一假期可以拼出9天长假</span></a><span class="title-content-mark ie-vertical c-text c-gap-left-small "></span></li><li class="hotsearch-item odd" data-index="2"><a class="title-content tag-width c-link c-font-medium c-line-clamp1" href="https://www.baidu.com/s?wd=%E5%A6%BB%E5%AD%90%E6%83%B3%E5%8D%96%E6%88%BF%E6%95%91%E5%84%BF%E9%81%AD%E4%B8%88%E5%A4%AB%E5%8F%8D%E5%AF%B9+%E6%80%92%E6%8F%90%E7%A6%BB%E5%A9%9A&amp;sa=fyb_n_homepage&amp;rsv_dl=fyb_n_homepage&amp;from=super&amp;cl=3&amp;tn=baidutop10&amp;fr=top1000&amp;rsv_idx=2&amp;hisfilter=1" target="_blank" ><div class="title-content-noindex" style="display: none;"></div><i class="c-icon title-content-top-icon c-color-red c-gap-right-small" style="display: none;">&#xe62e;</i><span class="title-content-index c-index-single c-index-single-hot2" style="display: ;">2</span><span class="title-content-title">妻子想卖房救儿遭丈夫反对 怒提离婚</span></a><span class="title-content-mark ie-vertical c-text c-gap-left-small c-text-hot">热</span></li><li class="hotsearch-item even" data-index="5"><a class="title-content  c-link c-font-medium c-line-clamp1" href="https://www.baidu.com/s?wd=%E8%B0%81%E5%9C%A8%E9%B9%A4%E5%B2%97%E5%B0%8F%E5%9F%8E%E5%96%9D38%E5%85%83%E4%B8%80%E6%9D%AF%E7%9A%84%E5%92%96%E5%95%A1&amp;sa=fyb_n_homepage&amp;rsv_dl=fyb_n_homepage&amp;from=super&amp;cl=3&amp;tn=baidutop10&amp;fr=top1000&amp;rsv_idx=2&amp;hisfilter=1" target="_blank" ><div class="title-content-noindex" style="display: none;"></div><i class="c-icon title-content-top-icon c-color-red c-gap-right-small" style="display: none;">&#xe62e;</i><span class="title-content-index c-index-single c-index-single-hot5" style="display: ;">5</span><span class="title-content-title">谁在鹤岗小城喝38元一杯的咖啡</span></a><span class="title-content-mark ie-vertical c-text c-gap-left-small "></span></li></ul></div><textarea id="hotsearch_data" style="display:none;">
    
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest01 {
        public static void main(String[] args {
            String content = "name=\"oq\" value=\"\"><input type=\"hidden\" name=\"rsv_pq\"" +
                    " value=\"0xb79379dd001c441c\"><input type=\"hidden\" name=\"rsv_t\" valu" +
                    "e=\"6ab2PWJNZs4OfZhgLwgMJI9gVsivBq7kMA9T8vJRItmCHeBsBD0QsnashhrZ\"><inp" +
                    "ut type=\"hidden\" name=\"rqlang\" value=\"en\"></form><div id=\"m\" clas" +
                    "s=\"under-searchbox-tips s_lm_hide \"><div id=\"lm-new\"></div></div><div" +
                    " id=\"s-hotsearch-wrapper\" class=\"s-isindex-wrap s-hotsearch-wrapper hid" +
                    "e \"><div class=\"s-hotsearch-title\"><a class=\"hot-title\" href=\"https:/" +
                    "/top.baidu.com/board?platform=pc&sa=pcindex_entry\" target=\"_blank\"><div " +
                    "class=\"title-text c-font-medium c-color-t\" aria-label=\"百度热搜\"><i cla" +
                    "ss=\"c-icon\">&#xe687;</i><i class=\"c-icon arrow\">&#xe613;</i></div></a><" +
                    "a id=\"hotsearch-refresh-btn\" class=\"hot-refresh c-font-normal c-color-gra" +
                    "y2\"><i class=\"c-icon refresh-icon\">&#xe619;</i><span class=\"hot-refresh-" +
                    "text\">换一换</span></a></div><ul class=\"s-hotsearch-content\" id=\"hotsearc" +
                    "h-content-wrapper\"><li class=\"hotsearch-item odd\" data-index=\"0\"><a cla" +
                    "ss=\"title-content  c-link c-font-medium c-line-clamp1\" href=\"https://www.b" +
                    "aidu.com/s?wd=%E4%BA%8C%E5%8D%81%E5%B1%8A%E4%BA%8C%E4%B8%AD%E5%85%A8%E4%BC%" +
                    "9A%E5%BC%80%E5%A7%8B%E4%B8%BE%E8%A1%8C&amp;sa=fyb_n_homepage&amp;rsv_dl=fy" +
                    "b_n_homepage&amp;from=super&amp;cl=3&amp;tn=baidutop10&amp;fr=top1000&amp" +
                    ";rsv_idx=2&amp;hisfilter=1\" target=\"_blank\" ><div class=\"title-content" +
                    "-noindex\" style=\"display: none;\"></div><i class=\"c-icon title-content-t" +
                    "op-icon c-color-red c-gap-right-small\" style=\"display: ;\">&#xe62e;</i><s" +
                    "pan class=\"title-content-index c-index-single c-index-single-hot0\" style=" +
                    "\"display: none;\">0</span><span class=\"title-content-title\">二十届二中全会" +
                    "开始举行</span></a><span class=\"title-content-mark ie-vertical c-text c-g" +
                    "ap-left-small \"></span></li><li class=\"hotsearch-item even\" data-index=\"" +
                    "3\"><a class=\"title-content  c-link c-font-medium c-line-clamp1\" href=\"h" +
                    "ttps://www.baidu.com/s?wd=%E7%AB%99%E4%B8%8A%E6%96%B0%E8%B5%B7%E7%82%B9+%E5" +
                    "%A5%8B%E5%8A%9B%E5%BC%80%E6%96%B0%E5%B1%80&amp;sa=fyb_n_homepage&amp;rsv_dl" +
                    "=fyb_n_homepage&amp;from=super&amp;cl=3&amp;tn=baidutop10&amp;fr=top1000&am" +
                    "p;rsv_idx=2&amp;hisfilter=1\" target=\"_blank\" ><div class=\"title-content" +
                    "-noindex\" style=\"display: none;\"></div><i class=\"c-icon title-content-t" +
                    "op-icon c-color-red c-gap-right-small\" style=\"display: none;\">&#xe62e;</" +
                    "i><span class=\"title-content-index c-index-single c-index-single-hot3\" st" +
                    "yle=\"display: ;\">3</span><span class=\"title-content-title\">站上新起点 奋" +
                    "力开新局</span></a><span class=\"title-content-mark ie-vertical c-text c-gap" +
                    "-left-small \"></span></li><li class=\"hotsearch-item odd\" data-index=\"1\"" +
                    "><a class=\"title-content tag-width c-link c-font-medium c-line-clamp1\" hr" +
                    "ef=\"https://www.baidu.com/s?wd=%E7%94%B7%E5%AD%90%E5%9C%A8%E8%87%AA%E5%AE%" +
                    "B6%E9%B1%BC%E5%A1%98%E7%94%B5%E9%B1%BC%E8%A2%AB%E7%BD%9A+%E8%AD%A6%E6%96%B9" +
                    "%E9%81%93%E6%AD%89&amp;sa=fyb_n_homepage&amp;rsv_dl=fyb_n_homepage&amp;from" +
                    "=super&amp;cl=3&amp;tn=baidutop10&amp;fr=top1000&amp;rsv_idx=2&amp;hisfilte" +
                    "r=1\" target=\"_blank\" ><div class=\"title-content-noindex\" style=\"displ" +
                    "ay: none;\"></div><i class=\"c-icon title-content-top-icon c-color-red c-ga" +
                    "p-right-small\" style=\"display: none;\">&#xe62e;</i><span class=\"title-c" +
                    "ontent-index c-index-single c-index-single-hot1\" style=\"display: ;\">1<" +
                    "/span><span class=\"title-content-title\">男子在自家鱼塘电鱼被罚 警方道歉</sp" +
                    "an></a><span class=\"title-content-mark ie-vertical c-text c-gap-left-smal" +
                    "l c-text-hot\">热</span></li><li class=\"hotsearch-item even\" data-index=" +
                    "\"4\"><a class=\"title-content  c-link c-font-medium c-line-clamp1\" href=" +
                    "\"https://www.baidu.com/s?wd=%E4%BA%94%E4%B8%80%E5%81%87%E6%9C%9F%E5%8F%AF" +
                    "%E4%BB%A5%E6%8B%BC%E5%87%BA9%E5%A4%A9%E9%95%BF%E5%81%87&amp;sa=fyb_n_homep" +
                    "age&amp;rsv_dl=fyb_n_homepage&amp;from=super&amp;cl=3&amp;tn=baidutop10&am" +
                    "p;fr=top1000&amp;rsv_idx=2&amp;hisfilter=1\" target=\"_blank\" ><div class" +
                    "=\"title-content-noindex\" style=\"display: none;\"></div><i class=\"c-ico" +
                    "n title-content-top-icon c-color-red c-gap-right-small\" style=\"display: " +
                    "none;\">&#xe62e;</i><span class=\"title-content-index c-index-single c-ind" +
                    "ex-single-hot4\" style=\"display: ;\">4</span><span class=\"title-content-" +
                    "title\">五一假期可以拼出9天长假</span></a><span class=\"title-content-mark " +
                    "ie-vertical c-text c-gap-left-small \"></span></li><li class=\"hotsearch-i" +
                    "tem odd\" data-index=\"2\"><a class=\"title-content tag-width c-link c-font" +
                    "-medium c-line-clamp1\" href=\"https://www.baidu.com/s?wd=%E5%A6%BB%E5%AD%90" +
                    "%E6%83%B3%E5%8D%96%E6%88%BF%E6%95%91%E5%84%BF%E9%81%AD%E4%B8%88%E5%A4%AB%E5%" +
                    "8F%8D%E5%AF%B9+%E6%80%92%E6%8F%90%E7%A6%BB%E5%A9%9A&amp;sa=fyb_n_homepage&am" +
                    "p;rsv_dl=fyb_n_homepage&amp;from=super&amp;cl=3&amp;tn=baidutop10&amp;fr=to" +
                    "p1000&amp;rsv_idx=2&amp;hisfilter=1\" target=\"_blank\" ><div class=\"title-c" +
                    "ontent-noindex\" style=\"display: none;\"></div><i class=\"c-icon title-content-" +
                    "top-icon c-color-red c-gap-right-small\" style=\"display: none;\">&#xe62e;</i><spa" +
                    "n class=\"title-content-index c-index-single c-index-single-hot2\" style=\"displ" +
                    "ay: ;\">2</span><span class=\"title-content-title\">妻子想卖房救儿遭丈夫反对 怒提离" +
                    "婚</span></a><span class=\"title-content-mark ie-vertical c-text c-gap-left-small" +
                    " c-text-hot\">热</span></li><li class=\"hotsearch-item even\" data-index=\"5\"><a" +
                    " class=\"title-content  c-link c-font-medium c-line-clamp1\" href=\"https://www.ba" +
                    "idu.com/s?wd=%E8%B0%81%E5%9C%A8%E9%B9%A4%E5%B2%97%E5%B0%8F%E5%9F%8E%E5%96%9D38%E5%" +
                    "85%83%E4%B8%80%E6%9D%AF%E7%9A%84%E5%92%96%E5%95%A1&amp;sa=fyb_n_homepage&amp;rsv_dl=f" +
                    "yb_n_homepage&amp;from=super&amp;cl=3&amp;tn=baidutop10&amp;fr=top1000&amp;rsv_idx=2" +
                    "&amp;hisfilter=1\" target=\"_blank\" ><div class=\"title-content-noindex\" style=\"di" +
                    "splay: none;\"></div><i class=\"c-icon title-content-top-icon c-color-red c-gap-right" +
                    "-small\" style=\"display: none;\">&#xe62e;</i><span class=\"title-content-index c-ind" +
                    "ex-single c-index-single-hot5\" style=\"display: ;\">5</span><span class=\"title-cont" +
                    "ent-title\">谁在鹤岗小城喝38元一杯的咖啡</span></a><span class=\"title-content-mark ie-v" +
                    "ertical c-text c-gap-left-small \"></span></li></ul></div><textarea id=\"hotsearch_da" +
                    "ta\" style=\"display:none;\">";
            // 找规律:编程正则表达式
            // <span class="title-content-title">二十届二中全会开始举行</span></a>
            // <span class="title-content-title">站上新起点 奋力开新局</span></a>
    
            // 1. 创建正则表达式对象
            String reg = "[\\d]</span><span class=\"title-content-title\">(\\S*\\s?\\S*</span></a>";
            Pattern pattern = Pattern.compile(reg;
    
            // 2. 创建匹配器对象,用于匹配判断
            Matcher matcher = pattern.matcher(content;
    
            // 循环匹配,匹配返回true,否则返回false
            while(matcher.find( {
                String group = matcher.group(1;  // 分组
                System.out.println(group;
            }
        }
    }
    

    2. Java中正则表达式的相关类

    java.util.regex 包主要包括三类: PatternMatcherPatternSyntaxException

      Pattern 对象是正则表达式的编译标识。没有提供公共的构造。 要创建一个模式,您必须首先调用其中一个 public static compile 方法,然后返回一个 Pattern 对象。 这些方法接受正则表达式作为第一个参数。

    • Matcher 对象是解析器和针对输入字符串执行匹配操作的发动机。像 Patter 类一样,也没有定义公共的构造函数,Matcher 通过调用对象上的 matcher 方法来获取 Pattern 对象。

    • PatternSyntaxException 对象是一个未经检查的异常,其指示在正则表达式模式中的语法错误。

    2.1 Pattern 类

    Pattern.comile(String regex 创建该正则表达式的对象。

    public static Pattern compile(String regex; // regex 为正则表达式的格式;
    // 返回该正则表达式的 Pattern 对象。
    
    public Matcher matcher(CharSequence input;  // CharSequence input 就是一个需要匹配的字符串。
    // 返回一个 Matcher 匹配器对象。
    

    2.1.1 Pattern 类中的一些标识属性

    在 Pattern 类定义的替代 compile,它接受一组影响匹配的方式的标志方法。flags 参数是一个位掩码,可以包含以下任何公共静态字段:

      Pattern.CANON_EQ

    • Pattern.CASE_INSENSITIVE

    • Pattern.COMMENTS

    • Pattern.DOTALL

      . 匹配任何字符,包括行终止符。默认情况下,此表达式与行终止符不匹配。Dotall 模式也可以通过嵌入式标志表达式启用(?s。(s 是“单行”模式的助记符,这在 Perl 中也被使用)。

    • Pattern.LITERAL

    • Pattern.MULTILINE

    • Pattern.UNICODE_CASE

    • Pattern.UNIX_LINES

      如下是一些 Pattern 类中常用的方法:

    public static boolean matches(String regex,CharSequence input; //判断该字符串中是否有符合该正则表达式的子字符串;有返回 true,没有返回 false。 
    //CharSequence 是一个接口,其中String 类实现了该接口。
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest02 {
    
        /**
         * public static boolean matches(String regex,CharSequence input 方法
         */
        public static void main(String[] args {
            String content = "jfdasij123";
    
            boolean matches = Pattern.matches("\\w*123", content;
            System.out.println(matches;
    
            boolean matches2 = Pattern.matches("\\d", "1";
            System.out.println(matches2;
        }
    }
    
    public String[] split(CharSequence input;  // 根据给定的正则表达式分割,将分割后的字符串存储到String数组中
    
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest02 {
    
    
        /**
         *public String[] split(CharSequence input 分割字符串
         */
        public static void main(String[] args {
            String content = "one:two:three:four:five";
    
            // 1.创建正则表达式对象
            Pattern pattern = Pattern.compile(":";  // 以 : 分割
    
            String[] split = pattern.split(content;
            for (String regStr : split {
                System.out.println(regStr;
            }
        }
    }
    

    split 方法是一种很好的工具,用于收集位于匹配模式两侧的文本。如下示例,该 split 方法可以从字符串“one2️⃣three4️⃣five” 中提取单词 “one two three four five”。

      public static String quote(String s

      尝试了下,没有搞懂是啥,返回的全是 "\Qxxx\E" 的串

    • public String toString(

    2.2 Matcher 类

    pattern.matcher(方法返回一个 Mathcer 对象。

    public Matcher matcher(CharSequence input;  // CharSequence input 就是一个需要匹配的字符串。
    // 返回一个 Matcher 匹配器对象。
    

    如下是 Matcher 类一些常用的方法

    2.2.1 索引方法

      public int start(:返回上一个匹配的起始索引。
    • public int start(int group:返回上次匹配操作期间给定组捕获的子序列的起始索引。
    • public int end(:返回最后一个字符匹配后的偏移量。
    • public int end(int group:返回在上一次匹配操作期间由给定组捕获的子序列的最后一个字符之后的偏移量。

    start 和 end 方法的使用:

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest02 {
    
        /**
         * matcher 中的 start / end 的使用
         */
        public static void main(String[] args {
            String content = "hello hello fdafd hello";
    
            // 1. 创建正则表达式对象
            Pattern pattern = Pattern.compile("\\bhello\\b";
    
            // 2. 创建匹配器对象
            Matcher matcher = pattern.matcher(content;
    
            // 循环匹配
            while(matcher.find( {
                System.out.print("start: " + matcher.start(;  // 返回所匹配的子字符串的起始下标位置
                System.out.print("\t"+"end: " + matcher.end(; // 返回所匹配的子字符串的结束下标位置 + 1
                System.out.println(;
            }
        }
    }
    
    

    2.2.2 检查方法

      public boolean lookingAt(:尝试将输入序列从区域开头开始与模式相匹配。
    • public boolean find(:尝试找到匹配模式的输入序列的下一个子序列。
    • public boolean find(int start:重置此匹配器,然后尝试从指定的索引开始找到与模式匹配的输入序列的下一个子序列。
    • public boolean matches(:尝试将整个区域与模式进行匹配。

    使用 matches 和 lookingAt 方法

    matches 和 lookingAt 方法都尝试将输入序列与模式进行匹配。然而,差异在于,matches 要求整个输入序列匹配,lookingAt 而不需要。两种方法始终从输入字符串的开头开始。

      lookingAt : 只要所匹配对象的字符串中含有满足该正则表达式中的规则的子字符串,则返回 true,若一个都没有满足该正则表达式的子串,才返回 false。
    • matches : 只有所匹配对象的字符串整体都满足该正则表达式中的规则,才返回 true,否则返回 false。
    package blogs.blog12;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest02 {
    
    
        /**
         * 使用 matches 和 lookingAt 方法
         */
        public static void main(String[] args {
            String content = "hiiiiiiiiiii";
    
            // 1. 创建正则表达式对象
            Pattern pattern = Pattern.compile("hi";
    
            // 2. 创建匹配器对象
            Matcher matcher = pattern.matcher(content;
    
            System.out.println("lookingAt(:" + matcher.lookingAt(;  // true 只要其中匹配的字符串存在满足该正则表达式的规则返回true,
                                                            
            System.out.println("matches(:" + matcher.matches(;  // false 必须要该匹配的字符串整体满足正则表达式的规则才返回 true
        }
    }
    
    

    2.2.3 替换方法

      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:返回 String 指定的文字替换 String。该方法产生一个在类的方法中 String 作为文字替换 s 的 appendReplacement 方法 Matcher。所产生的字符串 s 将作为字面序列处理。斜杠('\')和美元符号('$')将没有特殊意义。

    使用 replaceFirst(String and replaceAll(String

      replaceFirst(String : 替换满足给定的正则表达式的第一个子字符串的内容。
    • replaceAll(String : 替换满足给定的正则表达式的所有子字符串的内容。
    • 注意: 这两者都不会修改其原本的字符串内容,而是返回一个替换成功后的字符串。
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest02 {
        /**
         * 使用 replaceFirst(String and replaceAll(String
         */
        public static void main(String[] args {
            String content = "hello world hello world";
    
            // 1. 创建正则表达式对象
            Pattern pattern = Pattern.compile("hello";
    
            // 2. 创建匹配器对象
            Matcher matcher = pattern.matcher(content;
    
            String replaceAll = matcher.replaceAll("world";  // 替换全部
            System.out.println("replaceAll: " + replaceAll;
    
            String replaceFirst = matcher.replaceFirst("world";  // 替换第一个
            System.out.println("replaceFirst: " + replaceFirst;
    
    
        }
    }
    

    再看:

    a*b,输入aabfooaabfooabfoob 和替换字符串 -,在该表达式的匹配器上调用此方法将产生字符串 -foo-foo-foo-

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest02 {
        /**
         * 使用 replaceFirst(String and replaceAll(String
         */
        public static void main(String[] args {
            String content = "aabfooaabfooabfoob";
    
            // 1. 创建正则表达式对象
            Pattern pattern = Pattern.compile("a*b";
    
            // 2. 创建匹配器对象
            Matcher matcher = pattern.matcher(content;
    
            String replaceAll = matcher.replaceAll("-";  // 替换全部
            System.out.println("replaceAll: " + replaceAll;
    
            String replaceFirst = matcher.replaceFirst("-";  // 替换第一个
            System.out.println("replaceFirst: " + replaceFirst;
    
    
        }
    }
    

    a*b 表示 ab 或则 b 都符合条件,但是是贪婪量词,会出现长度零匹配的结果。所以就出现了上面的输出效果

    Matcher 类还提供 appendReplacementappendTail 文本替换方法。使用这两种方法来实现与之相同效果的 replaceAll

    2.3 PatternSyntaxException异常类

      public String getDescription(:检索错误的描述。
    • public int getIndex(:检索错误索引。
    • public String getPattern(:检索错误的正则表达式模式。
    • public String getMessage(:返回一个多行字符串,其中包含语法错误及其索引的描述,错误的正则表达式模式以及模式中错误索引的可视指示。

    2.4 String 类

    2.4.1 java.lang.String 与 java.util.regex.Pattern等效的几个方法

    java.lang.String 通过几种模仿行为的方法也存在正则表达式支持 java.util.regex.Pattern 中。为方便起见,他们的 API 的关键摘录如下。
    

      public boolean matches(String regex:

      str.matches(regex 和 Pattern.matches(regex, str

    • public String[] split(String regex, int limit:

      str.split(regex, n 和 Pattern.compile(regex.split(str, n

    • public String[] split(String regex:

    还有一个替换方法,替换CharSequence另一个:

      public String replace(CharSequence target,CharSequence replacement :

    2.4.2 java.lang.String 与 java.util.regex.Matcher 等效的几个方法

    为了方便起见,String 该类也模仿了几种 Matcher 方法:

      public String replaceFirst(String regex, String replacement:用给定的替换替换与给定正则表达式匹配的此字符串的第一个子字符串。这种形式的这种方法的调用产生与表达式完全相同的结果 str.replaceFirst(regex, replPattern.compile(regex.matcher(str.replaceFirst(repl
    • public String replaceAll(String regex, String replacement:用给定的替换替换与给定正则表达式匹配的此字符串的每个子字符串。这种形式的这种方法的调用产生与表达式完全相同的结果 str.replaceAll(regex, replPattern.compile(regex.matcher(str.replaceAll(repl

    3. 正则表达式的语法

    3.1 元字符: (\\ 转义字符

    转义字符(Escape Character)。它在***中是这么解释的:

    这么说可能有点不好理解,我再来给你通俗地解释一下。转义序列通常有两种功能。第一种功能是编码无法用字母表直接表示的特殊数据。第二种功能是用于表示无法直接键盘录入的字符(如回车符)。

    注意:在Java当中两个 \\ 才表示 一个 \

    需要用到转义符号的字符有大致有以下一些: {*,+,(,$,/,\,?,[],^,{},}

    举例: "匹配 (" 左括号,匹配 "$" 美元符号。

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest03 {
        public static void main(String[] args {
            String content = "abc$(abc123c";
    
            // 匹配 (创建正则表达式对象
            Pattern pattern = Pattern.compile("\\(";  // (需要使用转义,不然编译都无法通过 \\ 表示\
    
            // 匹配器
            Matcher matcher = pattern.matcher(content;
    
            // 循环匹配
            while(matcher.find( {
                String group = matcher.group(0;  // 整体没有分组的情况
                System.out.println(group;
            }
        }
    }
    

    举例: 匹配"." 单个 符号,如果不使用转义的话,单个点具有表示任意字符的意义。

    特殊的当 "." 是在 [.] 方括号中表示的就是 . 本身了,不需要转义。

    
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest03 {
        public static void main(String[] args {
            String content = "abc.$(abc123c.";
    
            // 匹配 (创建正则表达式对象
            Pattern pattern = Pattern.compile(".";  // 单个未转义的"."表示任意字符
    
            // 匹配器
            Matcher matcher = pattern.matcher(content;
    
            // 循环匹配
            while(matcher.find( {
                String group = matcher.group(0;  // 整体没有分组的情况
                System.out.println(group;
            }
        }
    }
    
    

    使用 “\\.” 转义后: 注意:这里其实一个 \ 就是表示转义的意思了,但是Java中想要表示一个 \ 就需要用两个 \\ 表示。

    3.2 元字符:字符匹配符

    注意: 如下的转义字符实际上是 单个 "\"的,但是因为在 Java中想要表示单个 斜杆“\” 就需要两个 "\\" 斜杆表示一个斜杆。

    符号 作用 示例 解释
    [ ] 可接受匹配的字符列表 [abcd] 可以匹配a,b,c,d 中的任意 1 个字符
    [^ ] ^表示取反的意思,不接受的字符列表 [^abc] 除了a,b,c 之外的任何 1 个字符,包括数字和特殊符号
    - 连字符:表示之间的意思 A-Z 表示任意 大写的 A到 Z 之间的字符。说白了就是任意大写字母
    . 个“点”:表示匹配除了 “\n” 以外的任何单个字符 a..b 以 a 开头,b 结尾,中间包含2 个任意字符(除了“\n” 长度为 4 的字符串。例如:aaab,aefb,a35b,a#*b
    \\d 匹配个数字字符,相当于 [0-9] \\d{3}(\\d? 包含3个或4个数字的字符串(其中的 ? 表示 0 个或1个)。例如:123,9876。
    \\D 匹配 个非数字字符,相当于[^0-9] \\D(\\d* 个非数字字符开头,后接任意个数字字符串(其中的 * 表示 0 个或 n 个)。例如:a,A123
    \\w 小写 w 匹配 个数字,大小写字母字符以及一个 “_” 下划线相当于[0-9a-zA-Z_ ] \\d{3}\\w 以 3 个数字字符开头后接4个数字/大小写字母/下划线的 长度为 7 的字符串。例如:234abcd,12345Pe,123___1
    \\W 大写的 W 匹配 个非数字,大小写字母字符,相当于 [^0-9a-zA-Z_ ] \\W+\\d 以至少1个非数字非字母字符开头的,2个数字字符结尾的字符串(其中的 + 表示 1 个或 n 个)。例如:@29,#?@10。
    \\s 小写的 s 表示:匹配任何空白字符(空格,制表符等
    \\S 大写的 S 表示:匹配任何非空白字符和 \\s 刚好相反
    \\n 匹配出 \n

    举例: 匹配三个连续的数字字符:

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 字符匹配符的使用
     */
    public class RegularTest04 {
        public static void main(String[] args {
    
            String content = "adc 999fajoi111 fajoidfj000";
    
            // 编写正则表达式
            String regStr = "\\d\\d\\d"; // \\d 表示[0-9]的数字字符
            // 或者
            String regStr2 = "\\d{3}";  // 表示三个连续的数字字符
    
            // 1. 创建正则表达式对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 2. 创建匹配器对象
            Matcher matcher = pattern.matcher(content;
    
            // 循环匹配
            while(matcher.find( {
                String group = matcher.group(0;
                System.out.println(group;
            }
    
    
        }
    }
    
    

    3.3 元字符正则表达式:不区分大小写的二种方式

    默认是区别字母的大小写的 。

    下面我来举个例子说明一下。在进行文本匹配时,我们要关心单词本身的意义。比如要查找单词 cat,我们并不需要关心单词是 CAT、Cat,还是 cat。根据之前我们学到的知识,你可能会把正则写成这样:[Cc][Aa][Tt],这样写虽然可以达到目的,但不够直观,如果单词比较长,写起来容易出错,阅读起来也比较困难。

    方式一:

    模式修饰符 :放在整个正则前面时,就表示整个正则表达式都是不区分大小写的。模式修饰符是通过 (? 模式标识 的方式来表示的。常用的有如下三种处理方式。

      (?iabc : 表示abc都不区分大小写。
    • a(?ibc : 表示 bc 不区分大小写,但是 a 是区分大小写的。
    • ab(?ic :表示只有 c 是不区分大小写的。
    • a((?ibc : 表示只有 b 是不区分大小写的。

    注意: (?i 模式修饰符,修饰的是后面的字符内容,不是前面的,被修饰的字符匹配时,是忽略大小写的。可以通过括号进行单个的修饰隔离。

    举例:

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 字符匹配符的使用
     */
    public class RegularTest04 {
    
        public static void main(String[] args {
            String content = "abc ABC aBC aBc Abc";
    
            // 1. 创建对应的正则表达式对象
            Pattern pattern1 = Pattern.compile("(?iabc";  // abc 都忽略大小写
            Pattern pattern2 = Pattern.compile("a(?ibc";  // bc 忽略大小写
            Pattern pattern3 = Pattern.compile("a((?ibc";  // 只有 b 是忽略大小写的
    
            // 2. 创建对应的 匹配器对象
            Matcher matcher1 = pattern1.matcher(content;
    
            // 3. 循环匹配
            System.out.print("abc 都忽略大小写: ";
            while (matcher1.find( {
                System.out.print(" " + matcher1.group(;
            }
    
            Matcher matcher2 = pattern2.matcher(content;
            System.out.print("\nbc 忽略大小写: ";
            while (matcher2.find( {
                System.out.print(" " + matcher2.group(;
            }
    
            Matcher matcher3 = pattern3.matcher(content;
            System.out.print("\n只有 b 是忽略大小写: ";
            while (matcher3.find( {
                System.out.print(" " + matcher3.group(;
            }
            System.out.println(;
        }
    }
    

    方式二:

    public static Pattern compile(String regex,int flags;// 将给定的正则表达式编译到具有给定标志的模式中。
    

    Pattern.CASE_INSENSITIVE

    举例:

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 字符匹配符的使用
     */
    public class RegularTest04 {
        public static void main(String[] args {
            String content = "abc Abc aBc ABC abC";
    
            // 1. 创建正则表达式对象,并设置忽略大小写
            Pattern pattern = Pattern.compile("abc",Pattern.CASE_INSENSITIVE;
    
            // 2. 创建匹配器
            Matcher matcher = pattern.matcher(content;
    
            // 3.循环匹配
            while(matcher.find( {
                String group = matcher.group(;
                System.out.println(group;
            }
        }
    }
    

    3.4 元字符:选择匹配符号

    "|"。

    符号 作用 示例 解释
    | 匹配 “|” 前后的表达式 ab|cd 匹配 ab 或者 cd

    举例:

    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 选择匹配符 |
     */
    public class RegularTest05 {
        public static void main(String[] args {
            String content = "你 您 拟 ";
    
            // 1. 创建正则表达式对象
            Pattern pattern = Pattern.compile("你|您";
    
            // 2. 创建匹配器
            Matcher matcher = pattern.matcher(content;
    
            // 3.循环匹配
            while(matcher.find( {
                String group = matcher.group(;
                System.out.println(group;
            }
        }
    }
    
    

    3.5 元字符:限定符

    符号 作用 示例 解释
    * 指定字符重复 0 次 或 n 次 : 0 到多 (abc* 仅包含任意个abc的字符串。例如:abc,abcabcabc
    + 指定字符重复 1 次或 n次: 至少一次 m+(abc* 以至少m个字符开头(1或多个m字母,后接任意个 abc的字符串。例如:m,mabc,mabcabc
    指定字符重复 0 次或 1 次:最多1次 m+abc? 以至少m个字符开头(1或多个m字母,后接ab或abc的字符串。例如:mab,mabc,mmmab。(注意 ? 修饰邻近的字符)
    只能输入n个字符 [abcd] 由abcd中字母组成的任意长度为3的字符串。例如:abc,dbc,abc
    指定至少 n 个匹配 [abcd] 由 abcd中字母组成的任意长度不小于3 的字符串,最多没有设置。例如:aab,dbc,aaabdc
    指定至少 n 个但不多于 m 个匹配 [abcd] 由abcd中字母组成的任意长度不小于3,不大于5 的字符串。例如:abc,abcd,aaaaa,bcdab

    举例:

    package blogs.blog12;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 限定符
     */
    public class RegularTest06 {
        public static void main(String[] args {
            String content = "a111111";
    
            //String regStr = "1+"; // 匹配一个或多个
    
            //String regStr = "\\d+";  // 匹配一个数字或者多个数字
    
            //String regStr = "1*";  // 匹配任意多个
    
            String regStr = "a1?";  // 匹配a 或者 a1 , 0 个或 1 个
    
    
            // 1. 创建正则表达式对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 2. 创建匹配器
            Matcher matcher = pattern.matcher(content;
    
            // 3.循环匹配
            while(matcher.find( {
                String group = matcher.group(;
                System.out.println(group;
            }
        }
    }
    
    

    3.6 贪婪匹配(Greedy)/ 非贪婪匹配(Lazy)

    3.6.1 贪婪匹配(Greedy)

    首先,我们来看一下贪婪匹配。在正则中,表示次数的量词 默认是贪婪的,在贪婪模式下,会尝试尽可能最大长度去匹配。

    a* 的匹配过程。

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 限定符
     */
    public class RegularTest06 {
    
        public static void main(String[] args {
            String content = "aaabb ";
    
            // 1. 创建正则表达式对象
            Pattern pattern = Pattern.compile("a*";
    
            // 2. 创建匹配器
            Matcher matcher = pattern.matcher(content;
    
            // 3.循环匹配
            while(matcher.find( {
                String group = matcher.group(;
                System.out.println(group;
            }
        }
    }
    
    字符串 aaabb
    下标 012345
    匹配 开始 结束 说明 匹配内容
    第 1 次 0 3 到第一个字母 b 发现不满足,输出 aaa aaa
    第 2 次 3 3 匹配剩下的 bb,发现匹配不上,输出空字符串 空字符串
    第 3 次 4 4 匹配剩下的 b,发现匹配不上,输出空字符串 空字符串
    第 4 次 5 5 匹配剩下的空字符串,输出空字符串 空字符串

    a* 在匹配开头的 a 时,会尝试尽量匹配更多的 a,直到第一个字母 b 不满足要求为止,匹配上三个 a,后面每次匹配时都得到了空字符串。

    贪婪模式的特点就是尽可能进行最大长度匹配。所以要不要使用贪婪模式是根据需求场景来定的。如果我们想尽可能最短匹配呢?那就要用到非贪婪匹配模式了。

    3.6.2 非贪婪匹配(Lazy)

    可以在量词后面加上英文的问号 (?,正则就变成了 a*?。此时的匹配结果

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 限定符
     */
    public class RegularTest06 {
    
        public static void main(String[] args {
            String content = "aaabb ";
    
            // 1. 创建正则表达式对象
            Pattern pattern = Pattern.compile("a+?";
    
            // 2. 创建匹配器
            Matcher matcher = pattern.matcher(content;
    
            // 3.循环匹配
            while(matcher.find( {
                String group = matcher.group(;
                System.out.println(group;
            }
        }
    

    到这里你可能就明白了,非贪婪模式会尽可能短地去匹配,我把这两者之间的区别写到了下面这张图中。

    3.7 元字符:定位符

    符号 作用 示例 解释
    ^ 指定起始字符 [1]+[a-z]* 以至少1个数字开头,后接任意个小写字母的字符串。例如:123,6aa,555edf
    $ 指定结束字符 [2]\\-[a-z]+$ 以至少1个数字开头,中间连接字符”-“,并以至少1个小写字母结尾的字符串。例如:1-a
    \\b 匹配目标字符串的边界。 han\\b (这里的边界可以是字符之间的空格/结尾)。例如:sphan nnhan可以匹配到两者中的 han
    \\B 匹配目标字符串的非边界 han\\B 和 \\b 的含义相反。例如:sphan nnhan。只能匹配到 nnhan中的 han

    举例:

    package blogs.blog12;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest07 {
        public static void main(String[] args {
            String content = "123-abc";  // a123abc  123abc  123abc12,
    
            //String regStr = "^[0-9]+[a-z]*"; // 以至少1个数字开头,后接任意个小写字母的字符串
    
            //String regStr = "^[0-9]+[a-z]+$";  // 以至少1个数字开头,必须以至少一个小写字母界结尾
    
            String regStr = "^[0-9]+\\-[a-z]+$";
    
            // 创建正则对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 创建匹配器
            Matcher matcher = pattern.matcher(content;
    
            // 循环
            while(matcher.find( {
                String group = matcher.group(0;
                System.out.println(group;
            }
        }
    }
    
    

    举例:

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest07 {
        public static void main(String[] args {
            String content = "hanshunping sphan nnhaan";
    
            //String regStr = "han\\b";  // 表示匹配边界 han ,边界:空格的子字符串的后面
    
            String regStr = "han\\B";  // 仅仅只是字符串的结尾,不含空格
    
            // 创建正则对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 创建匹配对象
            Matcher matcher = pattern.matcher(content;  // 该参数是个接口,String 实现了该接口
    
            // 循环
            while(matcher.find( {
                String group = matcher.group(;
                System.out.println(group;
            }
        }
    }
    

    4. 正则表达式:分组 / 特殊的分组

    常用的分组构造形式:

    在正则表达式中使用( 圆括号,括起来的就表示分组了 。

      ( 非命名捕获/也称为编号分组,捕获匹配的子字符串,编号为 0 的表示第一个分组,第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从 1 开始自动编号。
    • (? \<name> pattern : 命令捕获/也称为命名分组,将匹配的子字符串捕获到一个组名称或编号名称中,用于 name 的字符串不能包含任何标点符号,并且不能以数字开头,可以使用单引号代替尖括号。例如 ('?' name)。

    4.1 编号分组

    用于分组,被括号括起来的部分 「子表达式」会被保存成一个 子组

    第几个括号就是第几个分组。这么说可能不好理解,我们来举一个例子看一下。

    2020-05-10 20:23:05。假设我们想要使用正则提取出里面的日期和时间。

    日期是第 1 个,时间是第 2 个。

    注意: 不要分组越界了。就是说不要去访问没有分组的 group(的内容。比如说:你只分了两组,分别是 1,2 但是你却访问了一个不存在的分组 3,会报异常:java.lang.IndexOutOfBoundsException:

    补充:

    public String group(int group;  // Matcher 类中的对象方法,获取到分组/捕获到的分组中的内容。
    
      group(0/( :表示获取到符合正则表达式中整体的子字符串内容。如果不传参数默认调用的还是 group(0的,源码中可以看出来如下:
      group(1 :表示获取到从整体符合正则表达式的子字符串的基础上,进行第一个分组中的内容。
    • group(2 :表示获取到从整体符合正则表达式的子字符串的基础上,进行第二个分组中的内容。
    • group(3 ... 第3组,第4组,第5组都是以此类推的。
    • 注意不要越界了:不然报:java.lang.IndexOutOfBoundsException 异常。

    举例:

    package blogs.blog12;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 分组
     */
    public class RegularTest08 {
        public static void main(String[] args {
            String content = "2020-05-10 20:23:05";
    
            String regStr = "(\\d{4}-\\d{2}-\\d{2}.(\\d{2}:\\d{2}:\\d{2}";
    
            // 1.创建正则表达式对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 2. 创建匹配器对象
            Matcher matcher = pattern.matcher(content;
    
            // 3.循环匹配获取
            while (matcher.find( {
                String group = matcher.group(0;  // 0 / 不传参数,默认获取是整体正则表达式匹配的内容
                System.out.println("整体:日期和时间:" + group;
                String group1 = matcher.group(1; // 1 获取到表示第一个分组内容
                System.out.println("第一个分组(第一个圆括号的内容日期:" + group1;
                String group2 = matcher.group(2;  // 2获取到表示第二个分组的内容
                System.out.println("第二个分组(第二个圆括号的内容时间:" + group2;
    
            }
        }
    }
    
    

    注意不存在: group(3 第三组分组的内容,获取的话报异常

    4.2 命名分组

    可能导致编号发生变化,因此一些编程语言提供了 命名分组(named grouping),这样和数字相比更容易辨识,不容易出错。命名分组的格式为 (?P<分组名>正则。使用了命名分组,你即可以使用你命名的名称 / 自动编号的序号获取到对应分组中的内容。

    命名的注意事项:

    比如在获取四个连续的数字中,再分组获取两个连续的数字: 数值为 8899

    8899
    "(?<g1>\\d\\d(?<g2>\\d\\d";
    

    补充:

    public String group(String name; // Matcher 类中的对象方法,通过设置的分组名获取到对应匹配的分组内容。
    

    举例:

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 分组
     */
    public class RegularTest08 {
    
        public static void main(String[] args {
            String content = "9988fasjiofas";
    
            //String regStr = "(\\d\\d(\\d\\d";  // 匹配 4 个数字的字符串
    
            // 命名分组: 即可以给分组取名:
            String regStr = "(?<num1>\\d\\d(?<num2>\\d\\d";  // 注意命名不可以数字开头
    
            // 创建正则对象
            Pattern pattern = Pattern.compile(regStr; // String实现了该参数的接口
    
            // 创建匹配器
            Matcher matcher = pattern.matcher(content;
    
            // 循环匹配
            while (matcher.find( {
                String group = matcher.group(0;  // 整体分组
                System.out.println(group;
                //String group1 = matcher.group(1;  // 第一分组
                String group1 = matcher.group("num1";  // 第一分组
                System.out.println(group1;
                //String group2 = matcher.group(2;  // 第二分组
                String group2 = matcher.group("num2";  // 第二分组  可以用命名也可以用编号
                System.out.println(group2;
            }
        }
    }
    

    需要注意的是,刚刚提到的方式命名分组和前面一样,给这个分组分配一个编号,不过你可以使用名称,不用编号,实际上命名分组的编号已经分配好了。不过命名分组并不是所有语言都支持的,在使用时,你需要查阅所用语言正则说明文档,如果支持,那你才可以使用。

    4.3 特殊分组 / 不保存子组

    在括号里面的会保存成子组,但有些情况下,你可能只想用括号将某些部分看成一个整体,后续不用再用它,类似这种情况,在实际使用时,是没必要保存子组的。这时我们可以在括号里面使用 ?: 不保存子组。

    这个子表达式在后续可能会再次被引用,所以 不保存子组可以提高正则的性能。除此之外呢,这么做还有一些好处,由于子组变少了,正则性能会更好,在 子组计数时也更不容易出错

    括号只用于归组,把某个部分当成「单个元素」,不分配编号,后面不会再进行这部分的引用。

    表格中的 pattern 表示匹配的内容/正则表达式。注意: 该格式中的 ( 圆括号是不表示分组的,因为该分组的内容是不会存储起来的,更无法捕获到的。

    格式 说明
    (?:pattern 匹配 pattern,但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储不供以后使用,这对于用 ”or“ 字符(| 组合模式部件的情况很有用。例如:industr(?:y|ies 是比 'industry|industries' 更经济的表达式
    (?=pattern 它是一个非捕获匹配,例如:Windows(?=95|98|NT|2000 匹配 Windows 2000 中的 ”Windows" ,但不匹配 “Windows 3.1" 中的 Windows
    (?!pattern 该表达式匹配不处于匹配 pattern 的字符串的起始点的搜索字符串。它是一个非捕获匹配。例如:Windows(?!95|98|NT|200 匹配 ”Windows 3.1" 中的 ”Windows ",但不匹配Windows 2000 中的 “Windows”

    举例:(?:pattern) 的使用 从”李华校长 李华院长 李华老师 李华同学 “的字符串中找到 李华校长 李华院长 李华老师 李华同学

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 分组
     */
    public class RegularTest08 {
    
    
        /**
         *  1. 找到李华校长 李华院长 李华老师 李华同学 子字符串
         * 
         */
        public static void main(String[] args {
            String content = "李华校长 李华院长 李华老师 李华同学";
    
            String regStr = "李华(?:校长|院长|老师|同学";
            // 创建正则对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 创建匹配器对象
            Matcher matcher = pattern.matcher(content;
    
            // 循环
            while (matcher.find( {
                String group = matcher.group(0;
                //String group1 = matcher.group(1; 特殊分组中的括号不表示分组,没有分组就无法捕获到分组内容了
                System.out.println(group;
            }
        }
    }
    

    举例:(?=pattern 的使用 。从“李华校长 李华院长 李华老师 李华同学 “字符串中找到身份为校长院长”名为李华的名字。

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 分组
     */
    public class RegularTest08 {
    
    
        /**
         * 从“李华校长 李华院长 李华老师 李华同学 “字符串中找到身份为校长院长”名为李华的名字。
         *
         */
        public static void main(String[] args {
            String content = "李华校长 李华院长 李华老师 李华同学";
    
            String regStr = "李华(?=校长|院长";
            // 创建正则对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 创建匹配器对象
            Matcher matcher = pattern.matcher(content;
    
            // 循环
            while (matcher.find( {
                String group = matcher.group(0;
                //String group1 = matcher.group(1; 特殊分组中的括号不表示分组,没有分组就无法捕获到分组内容了
                System.out.println(group;
            }
        }
    }
    

    举例:(?!pattern的使用 。 从“李华校长 李华院长 李华老师 李华同学 “字符串中找到身份不是院长,校长,老师的李华姓名

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 分组
     */
    public class RegularTest08 {
    
        /**
         * 从“李华校长 李华院长 李华老师 李华同学 “字符串中找到身份不是院长,校长,老师的李华姓名
         *
         */
        public static void main(String[] args {
            String content = "李华校长 李华院长 李华老师 李华同学";
    
            String regStr = "李华(?!校长|院长|老师";
            // 创建正则对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 创建匹配器对象
            Matcher matcher = pattern.matcher(content;
    
            // 循环
            while (matcher.find( {
                String group = matcher.group(0;
                //String group1 = matcher.group(1; 特殊分组中的括号不表示分组,没有分组就无法捕获到分组内容了
                System.out.println(group;
            }
        }
    }
    

    4.4 分组中的括号嵌套

    我们只需要数左括号(开括号)是第几个,就可以确定是第几个子组。

    2020-05-10 20:23:05 。

    举例: 对 2020-05-10 20:23:05 日期时间进行 8 次分组。分为日期:年月日,时间:时分秒。

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 分组
     */
    public class RegularTest08 {
    
        /**
         *
         *  对 2020-05-10 20:23:05 日期时间进行 8 次分组。分为日期:年月日,时间:时分秒。
         */
        public static void main(String[] args {
            String content = "2020-05-10 20:23:05";
    
            String regStr = "((\\d{4}-(\\d{2}-(\\d{2}.((\\d{2}:(\\d{2}:(\\d{2}";
    
            // 1.创建正则表达式对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 2. 创建匹配器对象
            Matcher matcher = pattern.matcher(content;
    
            // 3.循环匹配遍历
            while(matcher.find( {
                System.out.println("日期时间:" + matcher.group(1;
                System.out.println("年:" + matcher.group(2;
                System.out.println("月:" + matcher.group(3;
                System.out.println("日:" + matcher.group(4;
                System.out.println("时间:" + matcher.group(5;
                System.out.println("时:" + matcher.group(6;
                System.out.println("分:" + matcher.group(7;
                System.out.println("秒:" + matcher.group(8;
            }
    
        }
    }
    

    5. 正则表达式的实现的原理分析:

    5.1 正则表达式中不含分组的原理分析

    首先我们来分析没有分组中的正则表达式的原理

    package blogs.blog12;
    
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * Java中正则表达式底层的实现原理
     */
    public class RegularTest09 {
        public static void main(String[] args {
            String content = "1995年5月23日,JAVA语言诞生(雏形)1996年,1997分别推出JDK1.0,JDK1.1版。" +
                    "1999年,JAVA被分成J2SE,J2EE,J2ME2000年," +
                    "JDK1.4发布2004年9月30日18:00PM,J2SE1.5发布,成为Java语言发展史上的又一里程碑。" +
                    "为了表示该版本的重要性,J2SE1.5更名为Java SE 5.0 2005年," +
                    "JAVA版本正式更名为JAVAEE,JAVASE, JAVAME2006年12月," +
                    "SUN公司发布JRE6.0 2009年04月20日,Oracle以74亿美元收购Sun。取得java的版权。 " +
                    "2010年9月,JDK7.0已经发布,增加了简单闭包功能。 " +
                    "2011年7月,甲骨文公司发布java7的正式版。2014年," +
                    "甲骨文公司发布了Java8正式版";
    
            // 这里我编写一个获取连续4个数字的子字符串
            String regStr = "\\d\\d\\d\\d";
    
            // 1. 创建正则表达式对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 2. 创建匹配器对象
            Matcher matcher = pattern.matcher(content;
    
            // 3.循环遍历匹配
            while(matcher.find( {
                String group = matcher.group(;
                System.out.println(group;
            }
        }
    }
    
    

    分析:

    (matcher.find( 方法的执行的任务流程:

    1. 找到后,将该子字符串的开始索引 (这里的1995 的起始下标是 0 )下标和 结束索引 (这里的1995 的结束下标是 3 )下标位置,记录到 Matcher 对象中的 int[] groups 数组中。

      1. groups[0] = 0,把该找到的子字符串下标位置存储到 groups[0] 中。再把该子字符串的“结束的索引 + 1” 的值,这里为 4,即下次执行 find( 时,就从 4 索引开始匹配了。
      2. 注意:groups[20] 数组默认容量是 20 ,如果不足会自动扩容,其中的默认值是 -1(字符串的索引下标是从 0 开始的,没有-1 这个下标索引。如下是我们 Debug 调试的结果
    1. 同时记录 odLast 的赋值为该子字符串的结束索引下标 + 1 的值,这里是 3 + 1 = 4 ,即下次执行 find( 方法时,就从 4 索引下标开始匹配了。
    1. 再次执行 find( 方法,从 oldLast 的值 开始匹配(这里是 4 )。覆盖第一个找到符合正则表达式的子字符串索引的在 groups[0]和groups[1] 数组当中的内容,这里因为没有分组所以对应的索引下标位置是仅仅只会存储到 groups[0]和groups[1] 数组下标位置。

      1. groups[0] = 23 值是:该匹配字符串的开始下标位置
      2. groups[1] = 27 值是:把该子字符串的“结束的索引” + 1的值。这里是 26 + 1 = 27。
      3. 同样 其中的 odLast 的赋值为该子字符串的结束索引下标 + 1 的值,这里是 26 + 1 = 27 ,即下次执行 find( 方法时,就从 27 索引下标开始匹配了。

      Debug 调试的结果

    1. ........ 如果再次执行 find( 方法,仍然是按照上面的方式找寻的。

    Matcher 类中的 group(方法的源码分析:

    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(;
        }
    

    5.2 正则表达式中含分组的原理分析

    package blogs.blog12;
    
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * Java中正则表达式底层的实现原理
     */
    public class RegularTest09 {
        public static void main(String[] args {
            String content = "1995年5月23日,JAVA语言诞生(雏形)1996年,1997分别推出JDK1.0,JDK1.1版。" +
                    "1999年,JAVA被分成J2SE,J2EE,J2ME2000年," +
                    "JDK1.4发布2004年9月30日18:00PM,J2SE1.5发布,成为Java语言发展史上的又一里程碑。" +
                    "为了表示该版本的重要性,J2SE1.5更名为Java SE 5.0 2005年," +
                    "JAVA版本正式更名为JAVAEE,JAVASE, JAVAME2006年12月," +
                    "SUN公司发布JRE6.0 2009年04月20日,Oracle以74亿美元收购Sun。取得java的版权。 " +
                    "2010年9月,JDK7.0已经发布,增加了简单闭包功能。 " +
                    "2011年7月,甲骨文公司发布java7的正式版。2014年," +
                    "甲骨文公司发布了Java8正式版";
    
            // 这里我编写一个获取连续4个数字的子字符串
            String regStr = "(\\d\\d(\\d\\d";
    
            // 1. 创建正则表达式对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 2. 创建匹配器对象
            Matcher matcher = pattern.matcher(content;
    
            // 3.循环遍历匹配
            while(matcher.find( {
                String group = matcher.group(0;  // 获取整体正则表达式内容
                System.out.println(group;
                String group1 = matcher.group(1;  // 获取在符合匹配整体正则表达式内容的基础上,再的第1个分组(编号是从左往右
                System.out.println(group1;
                String group2 = matcher.group(2;
                System.out.println(group2;       // 获取在符合匹配整体正则表达式内容的基础上,再的第2个分组(编号是从左往右
    
            }
        }
    }
    
    

    分析: 如下是我们 Debug 调试的结果

    正则表达式中含有的分组,其 matcher.find( 方法的执行流程和上面没有进行分组的流程是一样的。同样的地方就不多赘述了。

    分组: 比如(\d\d(\d\d
    正则表达式中有(表示分组,第1个(表示第1组,第2个表示第2组...(注意:编号分组是从左向右的圆括号开始的)
    根据指定的规则,定位满足正则表达式规则的子字符串(比如 (19(98)
    注意是先从整体上匹配正则表达式,也就是没有分组的时候: \d\d\d\d,只有到整体都匹配了
    才会再该整体的基础上,再进行分组
    找到后:将子字符串的开始索引记录到 matcher对象的属性 int[] groups数组当中

      groups[0] 记录的是整体匹配正则表达式的子字符串的起始位置;(1995 0
    • groups[1] 记录的是整体匹配正则表达式的子字符串的结束下标索引 +1 (1995 3+1= 4
    • groups[2] 记录的是再整体匹配正则表达式的基础上再分组的第1个分组的子字符串的起始下标位置: (1995(19 0
    • groups[3] 记录的是再整体匹配正则表达式的基础上再分组的第1个分组的子字符串的结束下标位置 + 1:(1995(19 1+1=2
    • groups[4] 记录的是再整体匹配正则表达式的基础上再分组的第2个分组的子字符串的起始下标位置: (1995(95 2
    • groups[3] 记录的是再整体匹配正则表达式的基础上再分组的第2个分组的子字符串的结束下标位置 + 1:(1995(95 3+1=4
    • 第3组,第4组 ...... 都还是以此类推 。

    6. 正则表达式:分组,捕获,反向引用

    分组: 我们可以用圆括号 “(” 组成一个比较复杂的匹配模式,那么一个圆括号的部分我们可以看作是一个子表达式/一个分组。

    捕获: 把正则表达式中子表达式/分组匹配的内容,保存到内存中以数字编号/显式命名的组里。方便后面引用,从左向右,以分组的左括号为标志,第一个出现的分组的组号为 1,第二个为 2,依次类推。组0 代表的是整个正则表达式。

    反向引用: 圆括号的内容被捕获后,可以在这个括号后被重复使用,从而写出一个比较实用的匹配模式。这个我们称为反向引用。这种引用即可以是在正则表达式内部使用,也可以在正则表示式的外部使用。内部反向引用使用 \\分组号,外部反向引用使用 $分组号

    反向引用在正则表达式中指定为反斜杠(\,后跟数字,表示要调用的组的编号。例如,表达式 (\d\d 定义了一行匹配一行中的两位数的捕获组,后者可以通过反向引用在表达式中调用该捕获组 \1注意:因为Java中的 想要表示 \ 反斜杠,是用两个 \\两个反斜杠表示的。

    反向引用 的主要作用就是:重复使用分组中的内容,因为分组中的内容是会存储起来的,所以我们可以使用反向引用重复使用。

    (\\d\\1 正则表达式:

    package blogs.blog12;
    
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 反向引用:
     *
     */
    public class RegularTest10 {
        public static void main(String[] args {
            String content = "hello jack14 tom11 jack222222 yyy  5225, 1551 xxx";
    
            // 反向引用在正则表达式内部中使用的 \\ 反斜杠的格式。
            String regStr = "(\\d\\1";  // 11,22 两个连续的数字 \\1反引用:表示重复使用第一个分组号中的内容
    
            // 创建正则对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 创建比较器对象
            Matcher matcher = pattern.matcher(content;
    
            while(matcher.find( {
                String group = matcher.group(;
                System.out.println(group;
            }
        }
    }
    
    
    

    举例: 要匹配个位与千位相同,十位与百位相同的数: 5225, 1551 (\\d(\\\d\\2\\1

    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 反向引用:
     *
     */
    public class RegularTest10 {
        public static void main(String[] args {
            String content = "hello jack14 tom11 jack222222 yyy  5225, 1551 xxx";
    
            // 反向引用在正则表达式内部中使用的 \\ 反斜杠的格式。
            // 要匹配个位与千位相同,十位与百位相同的数: 5225, 1551 (\\d(\\d\\2\\1
            String regStr = "(\\d(\\d\\2\\1"; // \\1反引用:表示重复使用第一个分组号中的内容
                                                // \\2 反引用:表示重复使用第2个分组号中的内容
    
            // 创建正则对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 创建比较器对象
            Matcher matcher = pattern.matcher(content;
    
            while(matcher.find( {
                String group = matcher.group(;
                System.out.println(group;
            }
        }
    }
    

    举例: 请在字符串中检索商品编号,形式如: 12321-333999111 要求满足前面是一个五位数,然后一个1号,然后是一个九位数,连续的每三位要相同。

    
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 反向引用:
     */
    public class RegularTest10 {
    
        /**
         * 请在字符串中检索商品编号,形式如: 12321-333999111
         * 要求满足前面是一个五位数,然后一个1号,然后是一个九位数,连续的每三位要相同。
         */
        public static void main(String[] args {
            String content = " 12321-333999111";
    
            String regStr = "\\d{5}-(\\d\\1{2}(\\d\\2{2}(\\d\\3{2}";
    
            // 创建正则对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 创建比较器对象
            Matcher matcher = pattern.matcher(content;
    
            while (matcher.find( {
                String group = matcher.group(;
                System.out.println(group;
            }
        }
    }
    

    7. 正则表达式:案例练习讲解

      正则表达式:经典的结巴程序把类似:"我...我要...学学学学...编程java!";通过正则表达式 修改成 "我要学编程Java"
    package blogs.blog12;
    
    import org.junit.Test;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest11 {
        /**
         * 正则表达式:经典的结巴程序
         * 把类似:"我...我要...学学学学...编程java!";
         * 通过正则表达式 修改成 "我要学编程Java"
         */
        @Test
        public void test( {
            String content = "我...我要...学学学学...编程java!";
    
            // 1.去掉所有的.
            String regStr = "\\.";
    
            // 正则对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 匹配器对象
            Matcher matcher = pattern.matcher(content;
            // 1. 去掉所有的. 使用 matcher.replaceAll("" 替换为 空
            content = matcher.replaceAll("";
    
            // 2. 去重复的子,我我要学学学学编程Java
            // 思路:
            //(1使用(.\\1+ 找到重复的
            //(2使用 反向引用$1 来替换匹配到的内容: 因为是在正则表达式外部了,反向引用格式是 $
            pattern = Pattern.compile("(.\\1+";
            matcher = pattern.matcher(content;
    
            System.out.println(content;
    
            // 循环
            while(matcher.find( {
                String group = matcher.group(0;
                System.out.println(group;
            }
            //(2使用 反向引用$1 来替换匹配到的内容: 因为是在正则表达式外部了,反向引用格式是 $
            content = matcher.replaceAll("$1";  //将 我我替换成 单个我,将学学学学替换成单个学
            System.out.println(content;
    
        }
    }
    
    

    方式二:一条语句完成

    
    import org.junit.Test;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest11 {
    
        /**
         * 方式二
         */
        // 方式2 一条语句完成:
        @Test
        public void test2( {
            String content = "我...我要...学学学学...编程java!";
    
            // 1.去除.
            content = Pattern.compile("\\.+".matcher(content.replaceAll("";
    
            content = Pattern.compile("(.\\1+".matcher(content.replaceAll("$1";
            System.out.println(content;
        }
    }
    
      替换功能;String 类 public String replaceAll(String regex,String replacement,参数1是正则表达式,参数2是字符串,将如下文字 JDK1.3 JDK1.4 统一替换成 JDK,fajiodfjasiofjioasjJDK1.3afsjoiadsjfoiasjJDK1.4。
    
    import org.junit.Test;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest11 {
    
    
        /**
         * 替换功能;
         * String 类 public String replaceAll(String regex,String replacement
         * 参数1是正则表达式,参数2是字符串
         * 将如下文字 JDK1.3 JDK1.4 统一替换成 JDK
         * fajiodfjasiofjioasjJDK1.3afsjoiadsjfoiasjJDK1.4
         */
        @Test
        public void test3( {
    
            String content = "fajiodfjasiofjioasjJDK1.3afsjoiadsjfoiasjJDK1.4";
    
            // 使用正则表达式,将JDK1.3和JDK1.4替换成JDK
            String regStr = content.replaceAll("JDK1\\.3|JDK1\\.4", "JDK";
            System.out.println(regStr;
        }
    }
    
      要求: 验证一个手机号,要求必须是以138 139 开头的,使用 String 类 public boolean matchers(String regex {} 方法。
    import org.junit.Test;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest11 {
    
        /**
         * 要求: 验证一个手机号,要求必须是以138 139 开头的
         * 使用 String 类 public boolean matchers(String regex {} 方法
         */
        @Test
        public void test4( {
            String content = "13812312123";
    
            boolean matches = content.matches("(138|139\\d{8}";
            System.out.println(matches;
        }
    }
    
      使用 String 类 public String[] split(String regex,将 "hello#abc-jack12smith~北京’,要求按照 # 或者 -或者 ~ 或者数字来分割
    
    import org.junit.Test;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest11 {
    
        /**
         * 使用 String 类 public String[] split(String regex
         * 将 "hello#abc-jack12smith~北京’
         * 要求按照 # 或者 -或者 ~ 或者数字来分割
         */
        @Test
        public void test5( {
            String content = "hello#abc-jack12smith~北京";
    
            String[] split = content.split("#|-|~|\\d+";
            for (String regStr : split {
                System.out.println(regStr;
            }
        }
    }
    
      验证电子邮件格式是否合法规定电子邮件规则为:
      只能有一个@
    1. @前面是用户名,可以是a-z,A-Z,0-9 _字符
    2. @后面是域名,并且域名只能是英文字母,比如: sohu.com 或者 tsinghua.org.cn
    3. 写出对应的正则表达式,验证输入的字符串是否为满足规则。
    
    import org.junit.Test;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest11 {
        /**
         * 验证电子邮件格式是否合法
         * 规定电子邮件规则为:
         * 1. 只能有一个@
         * 2. @前面是用户名,可以是a-z,A-Z,0-9 _字符
         * 3. @后面是域名,并且域名只能是英文字母,比如: sohu.com 或者 tsinghua.org.cn
         * 4.写出对应的正则表达式,验证输入的字符串是否为满足规则。
         */
        @Test
        public void test6( {
            String content = "f3213@qq.com";
            String regStr = "[\\w-]+@([a-zA-Z]+\\.+[a-zA-Z]+";
    
            boolean matches = content.matches(regStr;
            System.out.println(matches;
        }
    }
    
      要求:验证是不是整数或者小数这个题目要考虑正数和负数: 123 -345 34.89 -87.9 -0.01 0.45
    
    import org.junit.Test;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest11 {
        /**
         * 要求:验证是不是整数或者小数
         * 这个题目要考虑正数和负数
         * 123 -345 34.89 -87.9 -0.01 0.45
         */
        @Test
        public void test7( {
            //String content = "123 -345 34.89 -87.9 -0.01 0.45";
    
            String content = "0.56";
            // 思路先写简单的正则表达式
            // 在逐步完善
            //String regStr = "^\\d+$"; // 判断数值
    
            //String regStr = "^[-+]?\\d+(\\.\\d+?$";  // 包含正负和小数
            // 处理特殊情况 000123.45
            String regStr = "^[-+]?([1-9]\\d*|0(\\.\\d+?$";  // 包含正负和小数
            boolean matches = content.matches(regStr;
            System.out.println(matches;
        }
    }
    
      对 url 进行解析:https://www.sohu.com:8080/abc/index.htm

    获取域名: www.sohu.com
    端口: 8080

    
    import org.junit.Test;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegularTest11 {
        /**
         * 对 url 进行解析:
         * https://www.sohu.com:8080/abc/index.htm
         * 获取协议 https
         * 获取域名: www.sohu.com
         * 端口: 8080
         * 文件名: index.htm
         */
        @Test
        public void test8( {
            String content = "https://www.sohu.com:8080/abc/index.htm";
    
            String regStr = "^([a-zA-Z]+://([a-zA-z.]+:(\\d+[\\w-/]*/([\\w.]+$";
            //String regStr = "^([a-zA-Z]+://([a-zA-z.]+:(\\d+[\\w-/]*/([\\w.%¥#(特殊符号]+$"; 不够的时候还可以加
    
            // 正则对象
            Pattern pattern = Pattern.compile(regStr;
    
            // 匹配器
            Matcher matcher = pattern.matcher(content;
    
            if(matcher.matches( {  // 整体匹配,成功获取信息
                String group = matcher.group(1;
                System.out.println(group;
                String group2 = matcher.group(2;
                System.out.println(group2;
                String group3 = matcher.group(3;
                System.out.println(group3;
                String group4 = matcher.group(4;
                System.out.println(group4;
    
            } else {
                System.out.println("匹配失败";
            }
    
        }
    }
    

    8. 正则表达式:优化建议

    8.1 尽量准确表示匹配范围

    ".+?" 之外,我们可以写成 "[^"]+"。使用 [^"] 要比使用点号好很多,虽然使用的是贪婪模式,但它不会出现点号将引号匹配上,再吐出的问题。

      ".+":如果使用贪婪模式,那么将会导致第一遍,将最后一个引号 " 吃掉,再回溯(吐出来)匹配正则里面的最后一个引号
    • ".+?":使用了非贪婪模式,不会导致发生回溯
    • "[^"]+":贪婪模式,引号开头结尾,中间用中括号选用非引号的字符出现 1 到多次,也不会发生回溯

    8.2 提取出公共部分

    通过上面对 NFA 引擎的学习,相信你应该明白 (abcd|abxy 这样的表达式,可以优化成 ab(cd|xy,因为 NFA 以正则为主导,会导致字符串中的某些部分重复匹配多次,影响效率。

    th(?:is|at 要比 this|that 要快一些,但从可读性上看,后者要好一些,这个就需要用的时候去权衡,也可以添加代码注释让代码更容易理解。

    (^this|^that is 这样的,锚点部分也应该独立出来,可以写成比如 ^th(is|at is 的形式,因为锚点部分也是需要尝试去匹配的,匹配次数要尽可能少。

    8.3 出现可能性大的放左边

    由于正则是从左到右看的,把出现概率大的放左边,域名中 .com 的使用是比 .net 多的,所以我们可以写成. (?:com|net\b,而不是 \.(?:net|com\b

    8.4 只在必要时才使用子组

    括号可以用于归组,但如果某部分后续不会再用到,就不需要保存成子组。通常的做法是,在写好正则后,把不需要保存子组的括号中加上 ?: 来表示只用于归组。如果保存成子组,正则引擎必须做一些额外工作来保存匹配到的内容,因为后面可能会用到,这会降低正则的匹配性能。

    8.5 警惕嵌套的子组重复

    (.** 这个正则,匹配的次数会呈指数级增长,所以尽量不要写这样的正则。

    8.6 避免不同分支重复匹配

    9. 资料:常用的正则表达式格式

    一、校验数字的表达式
    
    1 数字:^[0-9]*$
    2 n位的数字:^\d{n}$
    3 至少n位的数字:^\d{n,}$
    4 m-n位的数字:^\d{m,n}$
    5 零和非零开头的数字:^(0|[1-9][0-9]*$
    6 非零开头的最多带两位小数的数字:^([1-9][0-9]*+(.[0-9]{1,2}?$
    7 带1-2位小数的正数或负数:^(\-?\d+(\.\d{1,2}?$
    8 正数、负数、和小数:^(\-|\+?\d+(\.\d+?$
    9 有两位小数的正实数:^[0-9]+(.[0-9]{2}?$
    10 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3}?$
    11 非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*{1,3}$ 或 ^\+?[1-9][0-9]*$
    12 非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
    13 非负整数:^\d+$ 或 ^[1-9]\d*|0$
    14 非正整数:^-[1-9]\d*|0$ 或 ^((-\d+|(0+$
    15 非负浮点数:^\d+(\.\d+?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
    16 非正浮点数:^((-\d+(\.\d+?|(0+(\.0+?$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
    17 正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*|([0-9]*[1-9][0-9]*\.[0-9]+|([0-9]*[1-9][0-9]*$
    18 负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*|([0-9]*[1-9][0-9]*\.[0-9]+|([0-9]*[1-9][0-9]*$
    19 浮点数:^(-?\d+(\.\d+?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
    
    
    
    
    二、校验字符的表达式
    
    
    1 汉字:^[\u4e00-\u9fa5]{0,}$
    2 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
    3 长度为3-20的所有字符:^.{3,20}$
    4 由26个英文字母组成的字符串:^[A-Za-z]+$
    5 由26个大写英文字母组成的字符串:^[A-Z]+$
    6 由26个小写英文字母组成的字符串:^[a-z]+$
    7 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
    8 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
    9 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
    10 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
    11 可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+
    12 禁止输入含有~的字符:[^~\x22]+
    
    
    
    
    三、特殊需求表达式
    
    
    1 Email地址:^\w+([-+.]\w+*@\w+([-.]\w+*\.\w+([-.]\w+*$
    2 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62}+/.?
    3 InternetURL:[a-zA-z]+://[^\s]* 或 ^https://([\w-]+\.+[\w-]+(/[\w-./?%&=]*?$
    4 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9]\d{8}$
    5 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX:^(\(\d{3,4}-|\d{3.4}-?\d{7,8}$ 
    6 国内电话号码(0511-4405222、021-87888822:\d{3}-\d{8}|\d{4}-\d{7}
    7 身份证号:
    		15或18位身份证:^\d{15}|\d{18}$
    		15位身份证:^[1-9]\d{7}((0\d|(1[0-2](([0|1|2]\d|3[0-1]\d{3}$
    		18位身份证:^[1-9]\d{5}[1-9]\d{3}((0\d|(1[0-2](([0|1|2]\d|3[0-1]\d{4}$
    8 短身份证号码(数字、字母x结尾:^([0-9]{7,18}(x|X?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
    9 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线:^[a-zA-Z][a-zA-Z0-9_]{4,15}$
    10 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线:^[a-zA-Z]\w{5,17}$
    11 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间:^(?=.*\d(?=.*[a-z](?=.*[A-Z].{8,10}$ 
    12 日期格式:^\d{4}-\d{1,2}-\d{1,2}
    13 一年的12个月(01~09和1~12:^(0?[1-9]|1[0-2]$
    14 一个月的31天(01~09和1~31:^((0?[1-9]|((1|2[0-9]|30|31$ 
    15 钱的输入格式:
    16 1.有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":^[1-9][0-9]*$ 
    17 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*$ 
    18 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*$ 
    19 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+?$ 
    20 5.必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:^[0-9]+(.[0-9]{2}?$ 
    21 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2}?$ 
    22 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3}*(.[0-9]{1,2}?$ 
    23 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3}*(.[0-9]{1,2}?$ 
    24 备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
    25 xml文件:^([a-zA-Z]+-?+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$
    26 中文字符的正则表达式:[\u4e00-\u9fa5]
    27 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1
    28 空白行的正则表达式:\n\s*\r (可以用来删除空白行
    29 HTML标记的正则表达式:<(\S*?[^>]*>.*?|<.*? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力
    30 首尾空白字符的正则表达式:^\s*|\s*$或(^\s*|(\s*$ (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等,非常有用的表达式
    31 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始
    32 中国邮政编码:[1-9]\d{5}(?!\d (中国邮政编码为6位数字
    33 IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用
    

    10. 总结

      正则表达式中默认是一个 \ 斜杆的,不过在Java中一个斜杆 需要两个 \\ 才能表示。
    1. 正则表达式默认是区分大小写的,可以设置不区分大小写的
    2. 正则表达式中默认是贪婪匹配:默认是匹配最多的可能结果。可以设置为非贪婪匹配
    3. 正则表达式中的分组/命名分组,默认分组编号是从左边第一个圆括号开始计数的,默认的 group[0] 是正则表达式整体,注意不要越界访问了。
    4. Java中执行正则表达式的原理流程。
    5. 反向引用的使用的格式:正则表达式内部 \\分组号,正则表达式外部使用格式是 :$分组号

    11. 最后:

    感谢以下大佬提供的参考资料 ✏️✏️✏️✏️✏️✏️✏️✏️✏️✏️ 👍👍👍

    【2】:https://zq99299.github.io/java-tutorial/essential/regex/

    【4】:https://www.w3cschool.cn/regexp/x9hf1pq9.html


    1. 0-9 ↩︎

    编程笔记 » Java 文本检索神器 "正则表达式"

    赞同 (144) or 分享 (0)
    游客 发表我的评论   换个身份
    取消评论

    表情
    (0)个小伙伴在吐槽