Java字符串 正则表达式详解
作者:半城抹茶
在日常Java后端开发过程中,免不了对数据字段的解析,自然就少不了对字符串的操作,这其中就包含了正则表达式这一块的内容,这里面涉及Java包中Pattern类和Macher类,本篇博客就针对这一块内容和常见的用法进行总结,本博客主要的参考资料是《Java编程思想》第4版。
以一个问题引出本博客的内容。问题是:检查一个字符串是否以大写字母开头,以句号结尾。
String len="^[A-Z].*[\\.]$"; System.out.println("Taaa.".matches(len));//true System.out.println("taaa.".matches(len));//false System.out.println("Taaa".matches(len));//false
1.*[\.] 就 是 正 则 表 达 式 , 用 来 匹 配 字 符 串 。 代 表 一 行 的 起 始 , [ A − Z ] 表 是 A 到 Z 任 何 的 字 母 , 就是正则表达式,用来匹配字符串。^代表一行的起始,[A-Z]表是A到Z任何的字母, 就是正则表达式,用来匹配字符串。代表一行的起始,[A−Z]表是A到Z任何的字母,表示的一行的结束。
一、规则表
规则表定义正则表达式该怎么写。这类东西不需要刻意去记,用的时候去写就好,用多了自然就记住了。
1.字符
B | 指定字符B |
---|---|
\xhh | 十六进制为oxhh的字符 |
\uhhhh | 十六进制表示为oxhhhh的Unicode字符 |
\t | 制表符Tab |
\n | 换行符 |
\r | 回车 |
\f | 换页 |
\e | 转义 |
2.字符类 .
. | 任意字符 |
---|---|
[abc] | 包含a、b和c的任何字符(和a|b|c作用相同) |
[^abc] | 除了a、b和c之外的任何字符(否定) |
[a-zA-Z] | 从a到z或从A到Z的任何字符(范围) |
[abc[hij]] | 任意a、b、c、h、i和j字符(与a|b|c|h|i|j作用相同) |
[a-z&&[hij]] | 任意h、i或j |
\s | 空白符(空格、tab、换行、换页和回车) |
\S | 非空白符 |
\d | 数字 |
\D | 非数字 |
\w | 词字符[a-zA-Z0-9] |
\W | 非词字符 |
3.边界匹配符
^ | 一行的起始 |
---|---|
$ | 一行的结束 |
\b | 词的边界 |
\B | 非词的边界 |
\G | 前一个匹配的结束 |
4.逻辑操作符
XY | Y跟在X后面 |
---|---|
X|Y | X或Y |
(X) | 捕获组 |
5.量词
贪婪型 | 勉强型 | 占有型 | 如何匹配 |
---|---|---|---|
X? | X?? | X?+ | 一个或零个X |
X* | X*? | X*+ | 零个或多个X |
X+ | X+? | X++ | 一个或多个X |
X{n} | X{n}? | X{n}+ | 恰好n次X |
X{n,} | X{n,}? | X{n,}+ | 至少n次X |
X{n,m} | X{n,m}? | X{n,m}+ | X至少n次,且不超过m次 |
二、Pattern类
1.Pattern类的实例获取—compile方法
1.下面主要是Pattern类源码下一些重要的方法
private Pattern(String p, int f) { pattern = p; flags = f; // to use UNICODE_CASE if UNICODE_CHARACTER_CLASS present if ((flags & UNICODE_CHARACTER_CLASS) != 0) flags |= UNICODE_CASE; // Reset group index count capturingGroupCount = 1; localCount = 0; if (!pattern.isEmpty()) { try { compile(); } catch (StackOverflowError soe) { throw error("Stack overflow during pattern compilation"); } } else { root = new Start(lastAccept); matchRoot = lastAccept; } }
从Pattern类代码可以看出构造器是私有的,故无法使用new去获得Pattern类对象实例。
仔细查询Pattern类下代码后发现通过静态方法compile去获取Pattern类的实例对象。
public static Pattern compile(String regex) { return new Pattern(regex, 0); }
通过compile方法生成一个Pattern类实例对象,然后将想要匹配的字段传入Pattern对象的matcher()方法,matcher()方法会返回生成一个Matcher类实例对象,这个对象也有很多对应的方法。所以Matcher类也是无法通过new去创建一个实例的,而是通过matcher方法
public Matcher matcher(CharSequence input) { if (!compiled) { synchronized(this) { if (!compiled) compile(); } } Matcher m = new Matcher(this, input); return m; }
public static boolean matches(String regex, CharSequence input) { Pattern p = Pattern.compile(regex); Matcher m = p.matcher(input); return m.matches(); }
所以之前博客开头那个问题,也可以用另外两种方式实现:
方式一:
Pattern len = Pattern.compile("^[A-Z].*[\\.]$"); Matcher matcher = len.matcher("Taaa."); boolean matches = matcher.matches(); System.out.println(matches);
当是从源码中可以看出matches方法是类静态方法,所以没必要使用Macher类实例对象来调用方法。可以使用:
方式二:
System.out.println(Pattern.matches("^[A-Z].*[\\.]$","Taaa."));
2.split方法
下面是Split方法的源码:
public String[] split(CharSequence input, int limit) { int index = 0; boolean matchLimited = limit > 0; ArrayList<String> matchList = new ArrayList<>(); Matcher m = matcher(input); // Add segments before each match found while(m.find()) { if (!matchLimited || matchList.size() < limit - 1) { if (index == 0 && index == m.start() && m.start() == m.end()) { // no empty leading substring included for zero-width match // at the beginning of the input char sequence. continue; } String match = input.subSequence(index, m.start()).toString(); matchList.add(match); index = m.end(); } else if (matchList.size() == limit - 1) { // last one String match = input.subSequence(index, input.length()).toString(); matchList.add(match); index = m.end(); } } // If no match was found, return this if (index == 0) return new String[] {input.toString()}; // Add remaining segment if (!matchLimited || matchList.size() < limit) matchList.add(input.subSequence(index, input.length()).toString()); // Construct result int resultSize = matchList.size(); if (limit == 0) while (resultSize > 0 && matchList.get(resultSize-1).equals("")) resultSize--; String[] result = new String[resultSize]; return matchList.subList(0, resultSize).toArray(result); }
方法解析:
input
:要拆分的字符序列;
limit
:结果阈值;根据指定模式拆分输入序列。
limit参数作用:
limit参数控制应用模式的次数,从而影响结果数组的长度。
如果 n 大于零
,那么模式至多应用 n- 1 次,数组的长度不大于 n,并且数组的最后条目将包含除最后的匹配定界符之外的所有输入。
如果 n 非正
,那么将应用模式的次数不受限制,并且数组可以为任意长度。
如果 n 为零
,那么应用模式的次数不受限制,数组可以为任意长度,并且将丢弃尾部空字符串。
详细讲解:假设 input=“boo:and:foo”,匹配符为"o",可知模式最多可应用4次,数组的长度最大为5;
1、当limit=-2时,应用模式的次数不受限制且数组可以为任意长度;推测模式应用4次,数组的长度为5,数组为{“b”,"",":and:f","",""};
2、当limit=2时,模式至多应用1次,数组的长度不大于 2,且第二个元素包含除最后的匹配定界符之外的所有输入;推测模式应用1次,数组的长度为2,数组为{“b”,“o:and:foo”};
3、当limit=7时,模式至多应用6次,数组的长度不大于 7;推测模式应用4次,数组的长度为5,数组为{“b”,"",":and:f","",""};
4、当limit=0时,应用模式的次数不受限制,数组可以为任意长度,并且将丢弃尾部空字符串;推测模式应用4次,数组的长度为3,数组为{“b”,"",":and:f"}。
这里m.find像迭代器,遍历输入字符串
3.Pattern中匹配标记参数
编译标记 | 效果 |
---|---|
Pattern.CANON_EQ | 启用规范等价。当且仅当两个字符的“正规分解(canonicaldecomposition)”都完全相同的情况下,才认定匹配。默认情况下,不考虑“规范相等性(canonical equivalence)”。 |
Pattern.CASE_INSENSITIVE | 启用不区分大小写的匹配。默认情况下,大小写不敏感的匹配只适用于US-ASCII字符集。这个标志能让表达式忽略大小写进行匹配,要想对Unicode字符进行大小不敏感的匹配,只要将UNICODE_CASE与这个标志合起来就行了。 |
Pattern.COMMENTS | 模式中允许空白和注释。在这种模式下,匹配时会忽略(正则表达式里的)空格字符(不是指表达式里的“\s”,而是指表达式里的空格,tab,回车之类)。注释从#开始,一直到这行结束。可以通过嵌入式的标志来启用Unix行模式。 |
Pattern.DOTALL | 启用dotall模式。在这种模式下,表达式‘.'可以匹配任意字符,包括表示一行的结束符。默认情况下,表达式‘.'不匹配行的结束符。 |
Pattern.MULTILINE | 启用多行模式。在这种模式下,‘^'和‘ ' 分 别 匹 配 一 行 的 开 始 和 结 束 。 此 外 , ‘ ' 仍 然 匹 配 字 符 串 的 开 始 , ‘ '分别匹配一行的开始和结束。此外,‘^'仍然匹配字符串的开始,‘ '分别匹配一行的开始和结束。此外,‘'仍然匹配字符串的开始,‘'也匹配字符串的结束。默认情况下,这两个表达式仅仅匹配字符串的开始和结束。 |
Pattern.UNICODE_CASE | 启用Unicode感知的大小写折叠。在这个模式下,如果你还启用了CASE_INSENSITIVE标志,那么它会对Unicode字符进行大小写不敏感的匹配。默认情况下,大小写不敏感的匹配只适用于US-ASCII字符集。 |
Pattern.UNIX_LINES | 启用Unix行模式。在这个模式下,只有‘\n'才被认作一行的中止,并且与‘.'、‘^'、以及‘$'进行匹配。 |
Pattern.CASE_INSENSITIVE
、Pattern.COMMENTS
、Pattern.MULTILINE
这三个比较常用。
三、Matcher类
对照Matcher构造器源码,可知构造器将Pattern对象的引用赋于Matcher中变量parentPattern,目标字符串赋于变量text;并创建了数组groups和locals 。
数组groups是组使用的存储。存储的是当前匹配的各捕获组的first和last信息。
groups[0]存储的是组零的first,groups[1]存储的是组零的last,groups[2]存储的是组1的first,groups[3]存储的是组1的last,依次类推。
Matcher类的方法非常多,这里就不对每个方法的源码进行详细的解读了,后续如果有空会深入研究一下。将常用方法总结如下:
方法名 | 功能作用 |
---|---|
public int groupCount() | 返回此匹配器中的捕获组数 |
public String group() | 实际上是调用了group(int group) ,只不过参数是0 |
public String group(int group) | 返回当前查找而获得的与组匹配的所有子串内容 |
public int start() | 返回当前匹配的子串的第一个字符在目标字符串中的索引位置 |
public int start(int group) | 返回当前匹配的指定组中的子串的第一个字符在目标字符串中的索引位置 。 |
public int end() | 返回当前匹配的子串的最后一个字符的下一个位置在目标字符串中的索引位置 。 |
public int end(int group) | 返回当前匹配的的指定组中的子串的最后一个字符的下一个位置在目标字符串中的索引位置 |
public boolean find() | 在目标字符串里查找下一个匹配子串 |
public boolean find(int start) | 重置此匹配器,然后尝试查找匹配该模式,从指定的位置开始查找下一个匹配的子串 |
public int regionStart() | 报告此匹配器区域的开始索引。 |
public int regionEnd() | 报告此匹配器区域的结束索引(不包括)。 |
public Matcher region(int start,int end) | 设置此匹配器的区域限制。重置匹配器,然后设置区域,使其从start参数指定的索引开始,到 end参数指定的索引结束(不包括end索引处的字符)。 |
public boolean lookingAt() | 从目标字符串开始位置进行匹配。只有在有匹配且匹配的某一子串中包含目标字符串第一个字符的情况下才会返回true。 |
public boolean matches() | 只有完全匹配时才会返回true。 |
public Matcher appendReplacement(StringBuffer sb, String replacement) | 将当前匹配子串替换为指定字符串,并将从上次匹配结束后到本次匹配结束后之间的字符串添加到一个StringBuffer对象中,最后返回其字符串表示形式。 |
public StringBuffer appendTail(StringBuffer sb) | 将最后一次匹配工作后剩余的字符串添加到一个StringBuffer对象里。 |
public String replaceAll(String replacement) | 将匹配的子串用指定的字符串替换。 |
public String replaceFirst(String replacement) | 将匹配的第一个子串用指定的字符串替换。 |
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!