推荐:
正则表达式 - 维基百科
正则表达式 - 菜鸟
Java正则表达式 - 菜鸟
重用正则总结 1 ([a-zA-Z -]*)([0-9]*) # 利用捕获组匹配,可匹配字符串示例:"abc90"、"90",第一组为字符串(空或abc),第二组为数字(90)。
正则表达式 定义
正则表达式定义了字符串的模式。
正则表达式可以用来搜索、编辑或处理文本。
正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。
转义
在 Java 中,\\
表示:我要插入一个正则表达式的反斜线,所以其后的字符具有特殊意义。
在其他语言中(如Perl),一个反斜杠 \
就足以具有转义的作用,而在 Java 中则需要有两个反斜杠才能被解析为其他语言中的转义作用。
Java 的正则表达式中,两个 \\
代表其他语言中的一个 \
,这也就是为什么表示一位数字的正则表达式是 \\d
,而表示一个普通的反斜杠是 \\\\
。
常用元字符 ^
、 $
、 *
、 +
、 ?
、 [a-z]
、 \w
、 \W
、 {n, m}
常见规则 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 A:字符 x 字符 x。 举例:'a'表示字符a \\ 反斜线字符。 两个杠才能表示一个杠,杠具有转义作用。 \n 新行(换行)符 ('\u000A') \r 回车符 ('\u000D') B:字符类 [abc] a、b 或 c(简单类) [^abc] 任何字符,除了 a、b 或 c(否定) [a-zA-Z] a到 z 或 A到 Z,两头的字母包括在内(范围) [0-9] 0到9的字符都包括 C:预定义字符类 . 任何字符(与行结束符可能匹配也可能不匹配)。'.' 字符本身,怎么表示呢? \. \d 数字:[0-9] \w 单词字符:[a-zA-Z_0-9] D:边界匹配器 ^ 行的开头 $ 行的结尾 \b 单词边界 不是单词字符的地方就是单词边界。 举例:hello world?haha;xixi 有三个单词边界 E:Greedy 数量词 X? X,一次或一次也没有 X* X,零次或多次 X+ X,一次或多次 X{n} X,恰好 n 次 X{n,} X,至少 n 次 X{n,m} X,至少 n 次,但是不超过 m 次
String类 String 类中有几个可以使用正则的方法,实际都是通过调用 Pattern、Matcher 类实现的
1 2 3 4 5 6 7 8 9 10 public boolean matches (String regex) public String replaceFirst (String regex, String replacement) public String replaceAll (String regex, String replacement) public String[] split(String regex)public String[] split(String regex, int limit)
Pattern和Matcher类 Pattern 类 :正则表达式的编译表示,没有公共构造方法。
使用方法:正则表达式字符串先被编译为此类的实例,然后用得到的 Pattern 对象创建 Matcher 对象。执行 matcher 方法后的所有匹配都驻留在匹配器 Matcher 中,所以多个匹配器可以共享同一模式。
1 2 3 4 public static Pattern compile (String regex) public static Pattern compile (String regex, int flags) public Matcher matcher (CharSequence input) public static boolean matches (String regex, CharSequence input)
Matcher 类 :没有公共构造方法。
通过解释 Pattern 对字符序列执行匹配操作的引擎 。
匹配器通过调用 Pattern 的 matcher 方法创建一个 Matcher 对象。创建后,Matcher 可用于执行三种不同类型的匹配操作:
matches 方法尝试将整个输入序列与模式匹配。
lookingAt 方法尝试将输入序列与模式匹配,从头开始。
find 方法扫描输入序列以查找与模式匹配的下一个子序列。
每个方法都返回一个表示成功或失败的布尔值。通过查询匹配器的状态可以获取关于成功匹配的更多信息。
如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。
1 2 3 public boolean matches () :尝试将整个区域与模式匹配。public boolean find () :尝试查找与该模式匹配的输入序列的下一个子序列。 如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。 public String group () :返回由以前匹配操作所匹配的输入子序列。以前匹配操作所匹配的字符串形式的子序列(可能为空)。
PatternSyntaxException 类 :一个非强制异常类,它表示一个正则表达式模式中的语法错误。
具体使用说明及更多方法见 API。
典型调用顺序
1 2 3 4 5 6 7 8 Pattern p = Pattern.compile("a*b" ); Matcher m = p.matcher("aaaaab" ); boolean b = m.matches();Pattern.compile(regex).matcher(input).matches() boolean b = Pattern.matches("a*b" , "aaaaab" )
练习
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 class RegexDemo { public static void main (String[] args) { Pattern p = Pattern.compile("a*b" ); Matcher m = p.matcher("aaaaab" ); boolean b = m.matches(); System.out.println(b); boolean bb = p.matcher("aaaaa" ).matches(); System.out.println(bb); String str = "aaaaab" ; String regex = "a*b" ; boolean cc = str.matches(regex); System.out.println(cc); } }
捕获组
捕获组是把多个字符当一个单独单元进行处理的方法,它通过对括号内的字符分组来创建。
捕获组是通过从左至右计算其开括号来编号。例如,在表达式 ((A)(B(C)))
,有四个这样的组:((A)(B(C)))
、(A)
、(B(C))
、(C)
。
Matcher 类的 groupCount 方法返回一个 int 值,表示 Matcher 对象有多个捕获组。
group(0) 是一个特殊的组,代表整个表达式。该组不包括在 groupCount 的返回值中。
用法:
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 line = "This order was placed for QT3000! OK?" ; Pattern pattern = Pattern.compile("(\\D*)(\\d+)(.*)" ); Matcher matcher = pattern.matcher(line); if (matcher.find()) { System.out.println("Found value: " + matcher.group(0 )); System.out.println("Found value: " + matcher.group(1 )); System.out.println("Found value: " + matcher.group(2 )); System.out.println("Found value: " + matcher.group(3 )); } else { System.out.println("NO MATCH" ); } } }
输出:
1 2 3 4 Found value: This order was placed for QT3000! OK? Found value: This order was placed for QT Found value: 3000 Found value: ! OK?
正则注入(regex injection) 定义
攻击者可能会通过恶意构造的输入对初始化的正则表达式进行修改,比如导致正则表达式不符合程序规定要求;可能会影响控制流,导致信息泄露,或导致ReDos攻击。
避免使用不可信数据构造正则表达式。
利用方式
匹配标志 :不可信的输入可能覆盖匹配选项,然后有可能会被传给 Pattern.compile()
方法。
贪赞 :一个非受信的输入可能试图注入一个正则表达式,通过它来改变初始的那个正则表达式,从而匹配尽可能多的字符串,从而暴露敏感信息。
分组 :程序员会用括号包括一部分的正则表达式以完成一组动作中某些共同的部分。攻击者可能通过提供非受信的输入来改变这种分组。
输入校验
非受信的输入应该在使用前净化,从而防止发生正则表达式注入。
当用户必须指定正则表达式作为输入时,必须注意需要保证初始的正则表达式没有被无限制修改。
在用户输入字符串提交给正则解析之前,进行白名单字符处理 (比如字母和数字)。
开发人员必须仅仅提供有限的正则表达式功能给用户,从而减少被误用的可能。
正则表达式拒绝服务 ( ReDoS ) 是一种算法复杂性攻击 ,它通过提供 正则表达式
或 需要很长时间评估的输入
来产生拒绝服务(即:通过提供特制的正则表达式或输入来使程序花费大量时间,消耗系统资源,然后程序将变慢或变得无响应)。
ReDos 攻击概述
JDK 中提供的正则匹配使用的是 NFA 引擎。
NFA 引擎具有回溯机制 (一个字符可能尝试多次匹配),匹配失败时花费时间很大。 正则表达式回溯法原理
当使用简单的非分组正则表达式时,是不会导致ReDos攻击的。
潜在危险
包含具有自我重复的重复性分组的正则 举例:^(\d+)+$
、^(\d*)*$
、^(\d+)*$
、^(\d+|\s+)*$
包含替换的重复性分组 举例:^(\d|\d|\d)+$
、^(\d|\d?)+$
当输入字符串为 1111111111111111111111x1
时,正则表达式 ^(\d+)+$
就会不断进行失败重试,从而耗死CPU计算。
解析: \d+
表示匹配一个或多个数字; ()+
表示分组本身也匹配一个或多个;那么匹配字符串 1111111111111111111111x1
就会进行非常多的尝试,从而导致CPU资源枯竭。
规避猎施
进行正则匹配前,先对匹配的文本 的长度进行校验 。
在编写正则时,尽量不要使用过于复杂的正则 ,越复杂越容易有缺陷。
在编写正则时,尽量减少分组 的使用。
避免动态构建正则 (因为难以判断是否有性能问题),当使用不可信数据构造正则时,要使用白名单进行严格校验。
案例 校验qq号码 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 37 38 39 40 41 42 43 44 45 public class RegexDemo { public static void main (String[] args) { Scanner sc = new Scanner (System.in); System.out.println("请输入你的QQ号码:" ); String qq = sc.nextLine(); System.out.println("checkQQ:" + checkQQ(qq)); } public static boolean checkQQ (String qq) { boolean flag = true ; if (qq.length() >= 5 && qq.length() <= 15 ) { if (!qq.startsWith("0" )) { char [] chs = qq.toCharArray(); for (int x = 0 ; x < chs.length; x++) { char ch = chs[x]; if (!Character.isDigit(ch)) { flag = false ; break ; } } } else { flag = false ; } } else { flag = false ; } return flag; } }
用正则表达式改进:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class RegexDemo { public static void main (String[] args) { Scanner sc = new Scanner (System.in); System.out.println("请输入你的QQ号码:" ); String qq = sc.nextLine(); System.out.println("checkQQ:" + checkQQ(qq)); } public static boolean checkQQ (String qq) { return qq.matches("[1-9]\\d{4,14}" ); } }
校验电话号码和邮箱
按照不同的规则分割数据 一:
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 class RegexDemo { public static void main (String[] args) { String ages = "18-24" ; String regex = "-" ; String[] strArray = ages.split(regex); for (int x = 0 ; x < strArray.length; x++) { System.out.println(strArray[x]); } int startAge = Integer.parseInt(strArray[0 ]); int endAge = Integer.parseInt(strArray[1 ]); Scanner sc = new Scanner (System.in); System.out.println("请输入你的年龄:" ); int age = sc.nextInt(); if (age >= startAge && age <= endAge) { System.out.println("你就是我想找的" ); } else { System.out.println("不符合我的要求,gun" ); } } }
二:
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 class RegexDemo { public static void main (String[] args) { String s1 = "aa,bb,cc" ; String[] str1Array = s1.split("," ); for (int x = 0 ; x < str1Array.length; x++) { System.out.println(str1Array[x]); } String s2 = "aa.bb.cc" ; String[] str2Array = s2.split("\\." ); for (int x = 0 ; x < str2Array.length; x++) { System.out.println(str2Array[x]); } String s3 = "aa bb cc" ; String[] str3Array = s3.split(" +" ); for (int x = 0 ; x < str3Array.length; x++) { System.out.println(str3Array[x]); } String s4 = "E:\\JavaSE\\day14\\avi" ; String[] str4Array = s4.split("\\\\" ); for (int x = 0 ; x < str4Array.length; x++) { System.out.println(str4Array[x]); } } }
三:
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 class RegexDemo { public static void main (String[] args) { String s = "91 27 46 38 50" ; String[] strArray = s.split(" " ); int [] arr = new int [strArray.length]; for (int x = 0 ; x < arr.length; x++) { arr[x] = Integer.parseInt(strArray[x]); } Arrays.sort(arr); StringBuilder sb = new StringBuilder (); for (int x = 0 ; x < arr.length; x++) { sb.append(arr[x]).append(" " ); } String result2 = sb.toString().trim(); System.out.println("result2:" + result2); } }
把论坛中的数字替换为* 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class RegexDemo { public static void main (String[] args) { String str = "hello!qq:12345;world!kh:622112345678;java!" ; String regex1 = "\\d+" ; String ss1 = "*" ; String result1 = str.replaceAll(regex1, ss1); System.out.println(result1); String regex2 = "\\d" ; String ss2 = "*" ; String result2 = str.replaceAll(regex2, ss2); System.out.println(result2); String regex3 = "\\d+" ; String ss3 = "" ; String result3 = str.replaceAll(regex3, ss3); System.out.println(result3); } }
获取字符串中由3个字符组成的单词 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class RegexDemo { public static void main (String[] args) { String s = "da jia ting wo shuo,jin tian yao xia yu,bu shang wan zi xi,gao xing bu?" ; String regex = "\\b\\w{3}\\b" ; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(s); while (m.find()) { System.out.println(m.group()); } } }