正则表达式基本简介 正则表达式(Regular Expression)是一种文本模式,包含数字,字母和以及特殊字符(元字符)等。从语法上来看,它虽然繁琐难以理解,但是在描述和处理文本方面它显得格外强大。很多编程语言都支持正则,包括但不限于C++、C#、Golang、java、python、js等等,而不同的语言其正则的语法亦略微有些差异。
正则表达式的的底层引擎实现有两种:DFA 自动机(Deterministic Final Automata 确定型有穷自动机)和 NFA 自动机(Non deterministic Finite Automaton 不确定型有穷自动机)。DFA自动机的时间复杂度是线性的,更加稳定,但是功能有限。而NFA的时间复杂度比较不稳定,有时候很好,有时候不怎么好,好不好取决于你写的正则表达式(可参考一个由正则表达式引发的血案 )。
下面将简单介绍一下java(8)- NFA 正则表达式的使用。
Java正则介绍 Java正则方面主要包含三个类:Pattern、Matcher、PatternSyntaxException,在java.util.regex包下。
Pattern类 在Pattern注释中是这样描述这个类的:A compiled representation of a regular expression(正则表达式的编译表示)。java中,一个正则表达式(字符串)必须编译成该类的实例,才能去被Matcher使用,一个pattern实例是可以被多个matcher进行使用的。
compile Pattern类不提供公共的构造函数,其只对外暴露了两个静态重载方法来编译构建pattern实例:
1 2 3 4 5 6 public static Pattern compile (String regex) { return new Pattern(regex, 0 ); } public static Pattern compile (String regex, int flags) { return new Pattern(regex, flags); }
由代码可见,Pattern的私有构造函数会有一个flags的参数,第一个函数默认给的是0,第二个是让使用者自己控制,那么flags这个参数有什么用呢?这个参数可以被赋予哪儿些值呢?
flags是匹配标志,一个位掩码,其可取值如下:
表达式
值
embedded flag
描述
Pattern.UNIX_LINES
0x01
(?d)
此模式下只有’\n’换行符被认可,且同’.’,’^’,’$’一样进行匹配
Pattern.CASE_INSENSITIVE
0x02
(?i)
默认情况下,大小写不敏感只针对US-ASCII字符,此模式在搭配UNICODE_CASE时可使得Unicode字符对大小写不敏感
Pattern.COMMENTS
0x04
(?x)
在此模式下,空格会被忽略,且以‘#’开头的行注释会被忽略
Pattern.MULTILINE
0x08
(?m)
此模式下,’^’和’$’分别匹配一行的开始和结束,此外,’^’仍然匹配字符串的开始,’$’也匹配字符串的结束。默认情况下,这两个表达式仅仅匹配字符串的开始和结束
Pattern.LITERAL
0x10
无
此模式下输入的字符串被认为是文字字符,其中的元字符和转移字符将失去其特殊意义。除了标志CASE_INSENSITIVE和UNICODE_CASE与此标志一起使用时保留其影响,其他标志会变得多余
Pattern.DOTALL
0x20
(?s)
此模式下’.’符号可以匹配任意字符,包含行结束符(默认情况不包含)
Pattern.UNICODE_CASE
0x40
(?u)
使用此模式,当同时使用CASE_INSENSITIVE模式时,匹配是对大小写不敏感
Pattern.CANON_EQ
0x80
无
启动规范等价,此模式下,如果且仅当它们的完整规范分解匹配时,将认为两个字符匹配
Pattern.UNICODE_CHARACTER_CLASS
0x100
(?U)
启用Unicode版本的预定义字符类和POSIX字符类
下面给出flags的一种使用情况,针对三种情况匹配正则忽略大小写问题:
flags = 0(默认), 预期结果不不忽略;
flags = Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE,预期结果忽略大小写;
正则加入(?i)且flags=0(默认),预期结果忽略大小写;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class RegexDemo { public static void main (String[] args) { System.out.println("====case one====" ); String regex = "Hello" ; String text = "Hello, word! hello!" ; Pattern pattern1 = Pattern.compile(regex); Matcher matcher1 = pattern1.matcher(text); while (matcher1.find()) { System.out.println("match1: " + matcher1.group()); } System.out.println("====case two====" ); Pattern pattern2 = Pattern.compile(regex, Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE); Matcher matcher2 = pattern2.matcher(text); while (matcher2.find()) { System.out.println("match2: " + matcher2.group()); } System.out.println("====case three====" ); String regex3 = "(?i)Hello" ; Pattern pattern3 = Pattern.compile(regex3); Matcher matcher3 = pattern3.matcher(text); while (matcher3.find()) { System.out.println("match3: " + matcher3.group()); } } }
输出结果如下,可以看到全部达到预期结果:
1 2 3 4 5 6 7 8 ====case one==== match1: Hello ====case two==== match2: Hello match2: hello ====case three==== match3: Hello match3: hello
matches 1 public static boolean matches (String regex, CharSequence input) ;
matches是Pattern类提供的静态公共方法,用于判断正则与输入的文本是否匹配.
quote 1 public static String quote (String s) ;
quote方法是Pattern提供的静态公共方法,其说明为“Metacharacters or escape sequences in the input sequence will be given no special meaning”,意义是输入序列中的元字符或转义序列将没有特殊含义,也就是说用此方法得到的一个字符串,其内部的 ‘.’ , ‘*’ 等特殊符号和’\n’等转义符号将变成普通的文本。
私有方法 下面是Pattern类下的私有方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public Matcher matcher (CharSequence input) ;public int flags () ;public String pattern () ;public String[] split(CharSequence input, int limit) ;public String[] split(CharSequence input);public Stream<String> splitAsStream (final CharSequence input) ;public Predicate<String> asPredicate () ;
这里来说明一下split方法,split方法时根据正则匹配的结果来分割输出的字符串数组。参数limit是控制匹配的次数,若limit = n,则最多匹配n-1次,limit=0表示全匹配。 下面用2中情况来说明:
limit=0(默认),预期结果为 {“小”, “你好,我是大”,”。”}
limit=2,预期结果为 {“小”,”你好,我是大A。”}
1 2 3 4 5 6 7 8 9 10 11 12 13 public class RegexDemo { public static void main (String[] args) { String regex = "(?i)A|a" ; String text = "小a你好,我是大A。" ; Pattern pattern = Pattern.compile(regex); String[] result1 = pattern.split(text); System.out.println("====limit 0====" ); Stream.of(result1).forEach(System.out::println); System.out.println("====limit 2====" ); String[] result2 = pattern.split(text, 2 ); Stream.of(result2).forEach(System.out::println); }
输出结果如下:
1 2 3 4 5 6 7 ====limit 0==== 小 你好,我是大 。 ====limit 2==== 小 你好,我是大A。
Matcher类 Matcher类是对输入字符串进行解释和匹配操作的引擎。其没有公共构造方法。是通过Pattern 对象的matcher方法来创建Matcher对象的。
quoteReplacement quoteReplacement是Matcher类提供的仅有的一个静态公共方法,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 public static String quoteReplacement (String s) { if ((s.indexOf('\\' ) == -1 ) && (s.indexOf('$' ) == -1 )) return s; StringBuilder sb = new StringBuilder(); for (int i=0 ; i<s.length(); i++) { char c = s.charAt(i); if (c == '\\' || c == '$' ) { sb.append('\\' ); } sb.append(c); } return sb.toString(); }
其作用是处理字符串文本中的‘$’和‘\’两个字符,将其从原本的特殊意义字符转义成普通字符。
校验是否匹配 Matcher类的校验API如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public boolean find () ;public boolean find (int start) ;public boolean matches () ;public boolean lookingAt () ;
下面通过代码来说明一下matchers和lookingAt方法的使用,首先是matches方法:
regex = “regex”; text = “regex” 预期结果true
regex = “regex”; text = “regex, matches”; 预期结果false
1 2 3 4 5 6 7 8 9 10 11 12 public class RegexDemo { public static void main (String[] args) { String regex = "regex" ; String text1 = "regex" ; String text2 = "regex, matches" ; System.out.println("====case one====" ); System.out.println(Pattern.matches(regex, text1)); System.out.println("====case two====" ); System.out.println(Pattern.matches(regex, text2)); } }
输出结果如下:
1 2 3 4 ====case one==== true ====case two==== false
其次是lookingAt方法:
regex = “regex”; text = “matches, regex”; 预期结 false
regex = “regex”; text = “regex, matches”; 预期结 true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class RegexDemo { public static void main (String[] args) { String regex = "regex" ; String text1 = "matches, regex" ; String text2 = "regex, matches" ; Pattern pattern = Pattern.compile(regex); System.out.println("====case one====" ); Matcher matcher1 = pattern.matcher(text1); System.out.println(matcher1.lookingAt()); System.out.println("====case two====" ); Matcher matcher2 = pattern.matcher(text2); System.out.println(matcher2.lookingAt()); } }
输出结果如下:
1 2 3 4 ====case one==== false ====case two==== true
定位匹配配置 Matcher类的定位查找方面的API如主要是group, start, end与region等,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public String group (int group) public String group () public String group (String name) public int groupCount () ;public int start () ;public int start (int group) ;public int start (String name) ;public int end () ;public int end (int group) ;public int end (String name) ;public Matcher region (int start, int end) ;public int regionStart () ;public int regionEnd () ;
下面通过使用代码来说明一下group和region的使用。首先是group,下面的代码将展示group几个重载方法的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class RegexDemo { public static void main (String[] args) { String regex1 = "(.)\\1{2,}" ; String regex2 = "(?<groupName>.)\\1{2,}" ; String text = "Hello, aaaaa!" ; Pattern pattern1 = Pattern.compile(regex1); Pattern pattern2 = Pattern.compile(regex2); Matcher matcher1 = pattern1.matcher(text); Matcher matcher2 = pattern2.matcher(text); if (matcher1.find()) { System.out.println("方法group()输出:" + matcher1.group()); } matcher1.reset(); if (matcher1.find()) { System.out.println("方法group(1)输出:" + matcher1.group(1 )); } if (matcher2.find()) { System.out.println("方法group(name)输出:" + matcher2.group("groupName" )); } } }
输出结果如下:
1 2 3 方法group()输出:aaaaa 方法group(1)输出:a 方法group(name)输出:a
接下来是region,通过下面的代码可以了解region的使用情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class RegexDemo { public static void main (String[] args) { String regex = "VIP|(?<wifi>wifi)|(?<vip>vip)|wifi" ; String text = "我用的wifi是vip的。" ; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(text); matcher = matcher.region(0 , 8 ); System.out.println("方法regionStart()输出:" + matcher.regionStart()); System.out.println("方法regionEnd()输出:" + matcher.regionEnd()); while (matcher.find()) { System.out.println("方法region(0,8)匹配:" + matcher.group()); } matcher = matcher.region(8 , 12 ); System.out.println("方法regionStart()输出:" + matcher.regionStart()); System.out.println("方法regionEnd()输出:" + matcher.regionEnd()); while (matcher.find()) { System.out.println("方法region(8,12)匹配:" + matcher.group()); } } }
输出结果如下:
1 2 3 4 5 6 方法regionStart()输出:0 方法regionEnd()输出:8 方法region(0,8)匹配:wifi 方法regionStart()输出:8 方法regionEnd()输出:12 方法region(8,12)匹配:vip
替换匹配文本 Matcher类文本替换相关的方法如下:
1 2 3 4 5 6 7 8 9 10 11 public String replaceFirst (String replacement) public String replaceAll (String replacement) ;public Matcher appendReplacement (StringBuffer sb, String replacement) ;public StringBuffer appendTail (StringBuffer sb) ;
这里着重看一下appendReplacement和appendTail的使用方法,下面的代码是展示用正则来剔除英文串的情况,同时若存在白名单中的单词不剔除:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class RegexDemo { private static final Pattern PATTERN_WHITE_NAME = Pattern.compile("(?i)VIP|wifi" ); private static final Pattern PATTERN_ENGLISH = Pattern.compile("[a-zA-Z]+" ); public static void main (String[] args) { String text = "我用的aaaAwifigood是viphelooooo的。" ; Matcher matcher = PATTERN_ENGLISH.matcher(text); StringBuffer sb = new StringBuffer(); while (matcher.find()) { String str = matcher.group(); matcher.appendReplacement(sb, processWhiteName(str)); } matcher.appendTail(sb); System.out.println(sb.toString()); } private static String processWhiteName (String str) { StringBuilder stringBuilder = new StringBuilder(); Matcher matcherWhiteName = PATTERN_WHITE_NAME.matcher(str); while (matcherWhiteName.find()) { stringBuilder.append(matcherWhiteName.group()); } return stringBuilder.toString(); } }
输出结果如下:
其他API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public Matcher reset () ;public Matcher reset (CharSequence input) ;public Matcher useAnchoringBounds (boolean b) public boolean hasAnchoringBounds () ;public Matcher useTransparentBounds (boolean b) public boolean hasTransparentBounds () public boolean hitEnd () ;public MatchResult toMatchResult () ;public Pattern pattern () ;public Matcher usePattern (Pattern newPattern) ;public boolean requireEnd () ;
PatternSyntaxException类 PatternSyntaxException顾名思义,是用来表示在正则在编译的时候是否有语法错误。提供了如下API:
1 2 3 4 5 6 7 8 public String getDescription () ;public int getIndex () ;public String getPattern () ;public String getMessage ()
通过一个代码示例来看一下API的使用情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class RegexDemo { public static void main (String[] args) { String regex = "((?i)[a-zA-Z]+" ; try { Pattern pattern = Pattern.compile(regex); } catch (PatternSyntaxException e) { System.out.println("getIndex输出: " + e.getIndex()); System.out.println("getDescription输出: " + e.getDescription()); System.out.println("getMessage输出: " + e.getMessage()); System.out.println("getPattern输出: " + e.getPattern()); } } }
输出结果:
1 2 3 4 5 6 getIndex输出: 14 getDescription输出: Unclosed group getMessage输出: Unclosed group near index 14 ((?i)[a-zA-Z]+ ^ getPattern输出: ((?i)[a-zA-Z]+