Java正则表达式-大白话手撕源码

1.介绍

正则表达式英文全称Regular Expression。一个正则表达式就是用某种模式去匹配字符串的一个公式,默认贪婪匹配。正则表达式不只是java语言独有的,许多编程语言都支持正则表达式对字符串的操作。

2.底层实现

要了解正则表达式的底层实现,那就要了解到两个类:PatternMatcher

2.1 一个简单案例

下面通过一个简单案例演示如何使用正则表达式完成匹配,找出符合要求的子串。该案例实现从一个字符串中找出所有的四位数。

代码:

public class RegExp_1 {
    public static void main(String[] args) {
        //匹配的字符串
        String content = "1999abx-#***2000ahj2001&2002";
        //正则表达式,\\d代表一个任意0-9的数字,四个\\d代表4个连在一起的数字
        String regex = "\\d\\d\\d\\d";
        //获得一个模式
        Pattern pattern = Pattern.compile(regex);
        //匹配
        Matcher matcher = pattern.matcher(content);
        while(matcher.find()){
            //打印匹配到的值
            System.out.println(matcher.group(0));
        }
    }
}

控制台输出:
案例1结果

2.2 案例解析

注意一下解析会搬出jdk源码,不需要去深究其中具体实现,只需要了解该方法有什么用?怎么使用?返回的值是什么?就可以了!

1.通过Pattern.compile(regex)方法传入正则表达式返回一个Pattern对象。下面这个代码块是jdk1.8中Pattern类的compile方法的源码,该方法是一个静态方法,返回一个Pattern对象。

public static Pattern compile(String regex) {
        return new Pattern(regex, 0);
    }

2.match意为匹配,调用Pattern类的matcher方法,传入需要匹配的字符串返回一个Mather对象。下面这个代码块是jdk1.8中Pattern类的matcher方法的源码。

public Matcher matcher(CharSequence input) {
        if (!compiled) {
            synchronized(this) {
                if (!compiled)
                    compile();
            }
        }
        Matcher m = new Matcher(this, input);
        return m;
    }

3.find意为找到,也就是找到了符合正则表达式的子串。通过查看Matcher类下该方法的源码,我们知道返回值是一个boolean类型的值。找到返回true,未找到返回false,因此我们在案例中调用并将其放在while循环中,使匹配直到再也找不到的时候才停止。

public boolean find() {
        int nextSearchIndex = last;
        if (nextSearchIndex == first)
            nextSearchIndex++;

        // If next search starts before region, start it at region
        if (nextSearchIndex < from)
            nextSearchIndex = from;

        // If next search starts beyond region then it fails
        if (nextSearchIndex > to) {
            for (int i = 0; i < groups.length; i++)
                groups[i] = -1;
            return false;
        }
        return search(nextSearchIndex);
    }

4.每找到一个匹配的子串,就打印一下matcher.group(0),而打印出来的东西刚好就是我们所需的结果。在展示的案例中有四个符合的子串,因此打印了4次。可以看出这个group()是重点,要解释清楚还是挺不容易的,在接下来的篇章中会重点解析。

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

1.首先group(int)返回的是一个字符串,案例中group(0)返回的是我们所需要的结果,说明这个方法返回的是一个字符串也是匹配成功的子串。这个int型的参数为什么取0先别管。

2.返回的是getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString(),也就是调用了getSubSequence这个方法然后将方法的返回值转成了字符串。所以我们查看getSubSequence的源码:

CharSequence getSubSequence(int beginIndex, int endIndex) {
        return text.subSequence(beginIndex, endIndex);
    }

3.显然getSubSequence是截取了text字符串的部分获得一个子串,那这个text究竟是什么,看看jdk源码怎么说:
在这里插入图片描述
text意为匹配的原始字符串,也就是案例中的content。

4.接下来我们只要搞清楚beginIndex和endIndex是怎么产生的,也就是弄清楚groups[ ]这个数组到底有什么用

为了了解groups[ ]里面有些什么,是怎么变化的,直接断点调试案例中的代码,在while(matcher.find())打断点:
在这里插入图片描述
(上图)可以看到matcher里有groups数组,初始化大小为20,全是-1;

在这里插入图片描述
(上图)往下走一步后,groups[0]=0,groups[l]=4

在这里插入图片描述
(上图)第二轮,groups[0]=12,groups[l]=16

在这里插入图片描述

(上图)第三轮,groups[0]=19,groups[l]=23

在这里插入图片描述

(上图)第四轮,groups[0]=24,groups[l]=28

四轮过后结束了。为什么会有4轮,因为案例中有四个匹配的子串,每一轮匹配到一个子串。通过观察会发现:groups只有下标0和1在变化。下标为0储存的值:匹配成功子串的开始下标,下标为1储存的值:匹配成功子串的终止下标+1!!!

5.所以第一轮的beginIndex=0,endIndex=4,截取了1999,以此类推…

2.4 group()的参数

这个参数与分组有关,先了解一下分组。
在正则表达式中,用()小括号括起来的就是一个分组,
现在修改案例代码如下:

public class RegExp_1 {
    public static void main(String[] args) {
        //匹配的字符串
        String content = "1999abx-#***2000ahj2001&2002";
        //正则表达式,\\d代表一个任意0-9的数字,四个\\d代表4个连在一起的数字
//        String regex = "\\d\\d\\d\\d";
        String regex2 = "(\\d\\d)(\\d\\d)";
        //获得一个模式
        Pattern pattern = Pattern.compile(regex2);
        //匹配
        Matcher matcher = pattern.matcher(content);

        int n = 0;
        while(matcher.find()){
            //打印匹配到的值
            System.out.println("匹配到的第"+(++n)+"个子串:"+matcher.group(0));
            System.out.println("第1个分组:"+matcher.group(1));
            System.out.println("第2个分组:"+matcher.group(2));
        }
    }
}

改进的代码中,正则表达式变为了两个分组,输出的多了group(1)和group(2)。

控制台输出:
在这里插入图片描述

可以很明显知道group(0)是打印整个子串,group(1)是打印子串的第1个分组,group(2)是打印子串的第二个分组。

为了说清楚这个group的参数,那追根到底就是要知道groups数组到底是怎么变化的,那再来debug一波:
在这里插入图片描述
(上图)第一轮,可以看到下标0-5都有了数值。推测一下不难得出0-1对应整个子串,2-3对应第一个分组,4-5对应第二个分组。不信可以再看以下几轮:
在这里插入图片描述
(上图)第二轮

在这里插入图片描述
(上图)第三轮

在这里插入图片描述
(上图)第四轮

四轮过后结束,不难看出groups数组记录的是整个子串以及分组在原字符串的开始下标与结束下标+1的数值。group(1)用到的就是groups数组下标为2-3的数值,group(2)用到的就是groups数组下标为4-5的数值,以此类推。

3.元字符

想要很好地使用正则表达式,必须了解各种元字符的功能,按功能分为以下六大类

3.1 限定符

用于指定其前面的字符和组合项连续出现多少次

符号含义
*指定字符重复0次或n次
+指定字符重复1次或n次(最少1次)
?指定字符重复0次或1次(最多1次)
{n}长度为n
{n,}指定至少n个字符匹配
{n}指定至少n个字符,至多m个字符匹配
在其它限定符后加上?变为非贪婪匹配

3.2 选择匹配符

符号含义
l匹配 l 之前或者之后的表达式

3.3 分组组合和反向引用符

3.3.1 捕获分组

常用分组构造形式说明
(pattern)非命名捕获。捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1开始自动编号。
?<name>(pattern)命名捕获。将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包含任何标点符号,并且不能以数字开头。

3.3.2 非捕获分组(特别分组)

常用分组构造形式说明
(?:pattern)匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用"or"字符组合模式部件的情况很有用。例如,industr(?:y l ies)是industry或industrie更经济的表达式。
(?=pattern)它是一个非捕获匹配。例如,“Windows (?=95[98[NT[2000)’匹配"Windows 2000"中的Windows”,但不匹配"Windows 3.1"中的"windows"。
(?!pattern)该表达式匹配不处于匹配pattern的字符串的起始点的搜索字符串。它是一个非捕获匹配。例如,“Windows (?I95]98[NT[2000)”匹配"Windows 3.1"中的“Windows”,但不匹配"Windows 2000"中的"Windows。.

3.3.3 反向引用

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

3.4 特殊字符

在使用正则表达式去匹配某些特殊符号时,需要用到转移符号(//),否则匹配不到结果甚至报错。
在java的正则表达式中,\代表其它语言中的\ !!!
需要用到转义字符的符号有:. * + ( ) & / \ ? [ ] ^ { }

3.5 字符匹配符

符号含义
[ ][abcd12] 匹配中括号中的任意一个字符
[^ ][^abcd12] 匹配除了中括号中的任意一个字符
-A-Z 匹配任意单个大写字母
.匹配除换行符(\n)以外的任意字符
\\d匹配单个数字字符 相当于[0-9]
\\D匹配单个非数字字符 相当于[^0-9]
\\w匹配单个数字、大小写字母字符 相当于[0-9a-zA-Z]
\\W匹配非单个数字、大小写字母字符 相当于[^0-9a-zA-Z]

3.6 定位符

规定要匹配的字符串出现的位置

符号含义
^指定开始字符
$指定结束字符
\\b匹配目标字符串的边界
\\B匹配目标字符串的非边界

4.正则表达式的三个常用类

java.util.regex包主要包括以下三个类Pattern类、Matcher类和PatternSyntaxException

4.1 Pattern

其中有两个常用方法:
1.静态方法compile(String regex),返回一个Pattern对象。

2.静态方法matches(String regex,CharSequence input)用于整体匹配,返回一个boolean值。

上代码:
在这里插入图片描述
在这里插入图片描述
正则表达式只匹配部分,返回了false。
在这里插入图片描述
在这里插入图片描述
正则表达式在原先的基础上加上了至少匹配一个数字,结果返回true。

4.2 Matcher

Matcher类中方法众多,在这里放一个总览图,就不一一展示了。
在这里插入图片描述

4.3 PatternSyntaxException

PatternSyntaxException是一个非强制异常类,它表示一个正则表达式模式中的语法错误。

5 实战演练

以下答案不唯一,仅供参考

T1.验证电子邮件格式是否合法
规定电子邮件规则为
1.只能有一个@
2.@前面是用户名,可以是a-z A-Z0-9_-字符
3.@后面是域名,并且域名只能是英文字母,比如sohu.com或者tsinghua.org.cn
4.写出对应的正则表达式,验证输入的字符串是否为满足规则

String regex1 = "^[a-zA-Z0-9_]+@([a-z]+\\.)+[a-zA-Z]+$";
String regex2 = "^[\\w_]+@([a-z]+\\.)+[a-z]+$";

T2.要求验证是不是整数或者小数
提示:这个题要考虑正数和负数
比如:123 -345 34.89 -87.9 -0.01 0.45等

String regex = "^-?(?:[1-9]\\d*|0)(\\.\\d+)*$";

T3.对一个url进行解析
https://www.sohu.com:8080/abc/index.html
a.要求得到协议是什么?
http
b.域名是什么?
www.sohu.com
c.端口是什么?
8080
d.文件名是什么?
index.html

public static void main(String[] args) {
        String content = "https://www.sohu.com:8080/abc/index.html";
        String regex = "^([a-zA-Z]+)://([a-zA-Z.]+):([1-9]\\d*)[\\w/]*/([\\w]+\\.[a-z]+)$";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("整体匹配:"+matcher.group(0));
            System.out.println("协议:"+matcher.group(1));
            System.out.println("域名:"+matcher.group(2));
            System.out.println("端口号:"+matcher.group(3));
            System.out.println("文件名:"+matcher.group(4));
        }
    }

结果:
在这里插入图片描述

T4.结巴去重
将字符串"java…是…是是.是…世界界…界上…最好的的.语语言"通过正则表达式转换为"java是世界上最好的语言"。

public static void main(String[] args) {
        String content = "java...是..是是.是.....世界界..界上..最好的的.语语言";
        String regex = "(.)";
        Pattern pattern = Pattern.compile("\\.");
        Matcher matcher = pattern.matcher(content);
        content = matcher.replaceAll("");
        System.out.println("去掉省略号后:"+ content);

        Pattern pattern1 = Pattern.compile("(.)\\1+");
        Matcher matcher1 = pattern1.matcher(content);
        content = matcher1.replaceAll("$1");
        System.out.println(("去重后:"+ content));
    }

结果:
在这里插入图片描述

到这里文章就结束了,欢迎各位批评指正,也别忘了点赞+评论+收藏

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
前言:本资来自于javaeye,原资链接地址:http://www.javaeye.com/topic/67398 原文如下: 以前写了一个java的正规表达式的java工具类,分享一下,有用到的欢迎下载使用。 如果你有常用的定义好的,且测试通过的正规表达式,欢迎跟贴,也让我享用一下 . 类中用到了 jakarta-oro-2.0.jar 包,请大家自己在 apache网站下下载 在这是junit测试单元类我就不提交了,在main()方法中有几个小测试,有兴趣自己玩吧. 这个工具类目前主要有25种正规表达式(有些不常用,但那时才仔细深入的研究了一下正规,写上瘾了,就当时能想到的都写了): 1.匹配图象; 2 匹配email地址; 3 匹配匹配并提取url ; 4 匹配并提取http ; 5.匹配日期 6 匹配电话; 7 匹配身份证 8 匹配邮编代码 9. 不包括特殊字符的匹配 (字符串中不包括符号 数学次方号^ 单引号' 双引号" 分号; 逗号, 帽号: 数学减号- 右尖括号> 左尖括号 0) 12 匹配正整数 13 匹配非正整数(负整数 + 0) 14 匹配负整数; 15. 匹配整数 ; 16 匹配非负浮点数(正浮点数 + 0) 17. 匹配正浮点数 18 匹配非正浮点数(负浮点数 + 0) 19 匹配负浮点数; 20 .匹配浮点数; 21. 匹配由26个英文字母组成的字符串; 22. 匹配由26个英文字母的大写组成的字符串 23 匹配由26个英文字母的小写组成的字符串 24 匹配由数字和26个英文字母组成的字符串; 25 匹配由数字、26个英文字母或者下划线组成的字符串; java码: /* * Created on 2005-4-15 * * Summary of regular-expression constructs 正则表达式结构简介: * Construct Matches * Characters 字符: * x The character x x 字符 x * \\ The backslash character \\ 反斜杠 * \0n The character with octal value 0n (0 <= n <= 7) \0n 十进制数 (0 <= n <= 7) * \0nn The character with octal value 0nn (0 <= n <= 7) \0nn 十进制数 0nn (0 <= n <= 7) * \0mnn The character with octal value 0mnn (0 <= m <= 3, 0 <= n <= 7) \0mnn 十进制数 0mnn (0 <= m <= 3, 0 <= n <= 7) * \xhh The character with hexadecimal value 0xhh \xhh 十六进制数 0x
卷积神经网络(CNN)是一种常用于图像处理和模式识别的深度学习模型。它的设计灵感来自于生物学中视觉皮层的神经元结构。为了用通俗的语言解释CNN,我们可以用以下方式来理解它: 假设你要识别一张猫的图片。首先,你的大脑会将这张图片的像素点转化成一系列数字,并且记录下它们的位置和颜色。然后,大脑会将这些数字输入到“卷积层”中。 在卷积层中,会有很多个“过滤器”。这些过滤器可以视为一双眼睛,它们通过抓取图片的不同特征来帮助你识别物体。每个过滤器都在图片上滑动并计算一个“特征图”,这个特征图描述了所检测到的特定特征。例如,一个过滤器可以检测到猫的边缘,另一个可以检测到猫的颜色等等。当所有过滤器完成计算后,就会得到一些不同的特征图。 在“池化层”中,每个特征图都会被压缩,去除一些不重要的信息。这样可以减少需要计算的数据量,并且使得特征更加鲁棒和不变形。 最后,在全连接层中,所有的特征图都被连接起来,形成一个巨大的向量。接下来,这个向量会通过一些神经元节点,最终输出识别结果,也就是“这是一张猫的图片”。 CNN的一个重要特点是参数共享,这意味着每个过滤器会在整个图片上进行计算,而不仅仅是某个局部区域。这样可以减少需要计算的参数量,提高训练速度和模型的泛化能力。 总结一下,CNN通过卷积层来提取图像的特征,并通过池化层降低特征的维度。最后,通过全连接层将所有特征连接起来并输出结果。这种结构使得CNN非常适合于图像分类和识别任务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

壹默学编码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值