java 正则表达式

java 正则表达式

正则表达式是一个非常强大的字符串处理工具,通过一种特殊的语法来描述一种模式,再通过模式可以完成字符串的匹配,萃取,替换等操作

简例

要判断一个字符串是否是一个邮箱,可能需要很多的判断逻辑,使用则表达式,只需要下面代码即可

Pattern pattern = Pattern.compile("^([a-z0-9._%+-]+)@([a-z0-9.-]+)\\.[a-z]{2,4}$");
Matcher matcher = pattern.matcher("hatlonely@foxmail.com");
assertTrue(matcher.matches());

主要有 Pattern 对象和 Matcher 对象

  • Pattern: 模式对象
  • Matcher: 匹配的结果

部分匹配与完全匹配

Matcher 提供 matches 方法用于完全匹配,find 方法用于部分匹配,还提供 asPredicate 方法返回一个部分匹配的谓词

Pattern 提供一个静态方法 matches 用于完全匹配,这个方法会临时构造 Pattern 对象,如果 Pattern 会被多次重复使用,尽量别直接使用这种方法

assertTrue(Pattern.compile("hello").matcher("hello").matches());         // 完全匹配
assertTrue(Pattern.compile("hello").matcher("hello world").find());      // 部分匹配
assertTrue(Pattern.compile("hello").asPredicate().test("hello world"));  // 部分匹配
assertTrue(Pattern.matches("hello", "hello"));                           // 完全匹配

通配符

符号 含义
c 匹配单个字符 c
. 匹配所有单个字符,换行符 \n 除外
^ 匹配字符串开始
$ 匹配字符串结束
\b 匹配字符界,字符和空白之间
\B 匹配非字符界,字符和空白之间
| 匹配前面表达式或者后面表达式
[charset] 匹配任意括号内的字符,可用 - 表示范围,[a-z] 表示所有小写字母
[^charset] 匹配任意括号外的字符
\d 匹配数字,相当于 [0-9]
\D 匹配非数字,相当于 [^0-9]
\s 匹配空白符,相当于 [ \f\n\r\t\v]
\S 匹配非空白符,相当于 [^ \f\n\r\t\v]
\f 匹配换页符
\n 匹配换行符
\r 匹配回车
\t 匹配字表符
\v 匹配垂直制表符
\w 匹配字符类字符,包括下划线,相当于 [A-Za-z0-9_]
\W 匹配非字符类字符,[^A-Za-z0-9_]
\u 匹配四位十六进制数表示的 Unicode 字符,\u00A9 例如匹配 ©
\x 匹配两位十六进制数表示的 ascii 码
限定符 含义
* 匹配前面字符或表达式 0 次或多次
+ 匹配前面字符或表达式 1 次或多次
? 匹配前面字符或表达式 0 次或1次,跟着其他限定词后表示非贪婪匹配
{n} 匹配前面字符或表达式 n 次
{n,} 匹配前面字符或表达式至少 n 次
{n,m} 匹配前面字符或表达式至少 n 次,至多 m 次
assertTrue(Pattern.matches("[0-9]*", ""));
assertTrue(Pattern.matches("[0-9]?", ""));
assertTrue(Pattern.matches("[0-9]?", "1"));
assertTrue(Pattern.matches("[0-9]+", "123"));
assertTrue(Pattern.matches("[0-9]{3}", "123"));
assertTrue(Pattern.matches("[0-9]{3,4}", "123"));
assertTrue(Pattern.matches("[0-9]{3,4}", "1234"));
assertTrue(Pattern.matches("[0-9]{3,}", "1234"));
assertTrue(Pattern.matches("\\d+", "123"));
assertTrue(Pattern.matches("\\s+", " \t"));
assertTrue(Pattern.matches("\\w+", "abc"));
assertTrue(Pattern.matches("(f|z)oo", "foo"));
assertTrue(Pattern.matches("(f|z)oo", "zoo"));
assertTrue(Pattern.matches(".*", "any string"));

捕获分组

支持用小括号 () 将模式分组,Matcher 提供 group 方法获取分组的内容

符号 含义
(pattern) 匹配分组并且捕获子表达式
(?:pattern) 匹配分组但是不捕获子表达式

使用 groupCount 获取捕获分组的数量,由于下面例子中模式串中没有小括号,所以没有捕获的分组

Pattern pattern = Pattern.compile("^[a-z0-9]+@[a-z0-9.]+[.][a-z]{2,4}$");
Matcher matcher = pattern.matcher("hatlonely@foxmail.com");
assertTrue(matcher.matches());
assertEquals(matcher.groupCount(), 0);

使用 group 方法获取分组的内容,分组的编号以左括号为准,gruop(i) 返回第 i 个分组,group(0) 代表整个匹配串

Pattern pattern = Pattern.compile("^([a-z0-9]+)@(([a-z0-9.]+)[.]([a-z]{2,4}))$");
Matcher matcher = pattern.matcher("hatlonely@foxmail.com");
assertTrue(matcher.matches());
assertEquals(matcher.groupCount(), 4);
assertEquals(matcher.group(), "hatlonely@foxmail.com");
assertEquals(matcher.group(1), "hatlonely");
assertEquals(matcher.group(2), "foxmail.com");
assertEquals(matcher.group(3), "foxmail");
assertEquals(matcher.group(4), "com");

可以用 ?: 阻止捕获,如下面代码所示,foxmail.com 就没有被捕获

Pattern pattern = Pattern.compile("^([a-z0-9]+)@(?:([a-z0-9.]+)[.]([a-z]{2,4}))$");
Matcher matcher = pattern.matcher("hatlonely@foxmail.com");
assertTrue(matcher.matches());
assertEquals(matcher.groupCount(), 3);
assertEquals(matcher.group(), "hatlonely@foxmail.com");
assertEquals(matcher.group(0), "hatlonely@foxmail.com");
assertEquals(matcher.group(1), "hatlonely");
assertEquals(matcher.group(2), "foxmail");
assertEquals(matcher.group(3), "com");

预测

符号 含义
(?=pattern) 正向预测,非捕获匹配,匹配结束
(?!pattern) 反向预测,非捕获匹配,匹配结束
(?<=pattern) 正向预测,非捕获匹配,匹配开始
(?<!pattern) 反向预测,非捕获匹配,匹配开始

正向预测是指匹配开始或者结束的字符串需要匹配(或者反向预测不能匹配)分组,分组内的模式仅用于预测,不会出现在最终的匹配串中,这种匹配只能用于部分匹配,?=?! 匹配开始,?<=?<! 匹配结束

{
    Pattern pattern = Pattern.compile("Windows (?=95|98|NT|2000)");
    Matcher matcher = pattern.matcher("Windows 2000");
    assertTrue(matcher.find());
    assertEquals(matcher.group(), "Windows ");
    assertEquals(matcher.groupCount(), 0);
}
{
    Pattern pattern = Pattern.compile("Windows (?!95|98|NT|2000)");
    Matcher matcher = pattern.matcher("Windows vista");
    assertTrue(matcher.find());
    assertEquals(matcher.group(), "Windows ");
    assertEquals(matcher.groupCount(), 0);
}
{
    Pattern pattern = Pattern.compile("(?<=95|98|NT|2000) Windows");
    Matcher matcher = pattern.matcher("2000 Windows");
    assertTrue(matcher.find());
    assertEquals(matcher.group(), " Windows");
    assertEquals(matcher.groupCount(), 0);
}
{
    Pattern pattern = Pattern.compile("(?<!95|98|NT|2000) Windows");
    Matcher matcher = pattern.matcher("vista Windows");
    assertTrue(matcher.find());
    assertEquals(matcher.group(), " Windows");
    assertEquals(matcher.groupCount(), 0);
}

反向引用

符号 含义
\1 匹配捕获匹配到的反向引用,\2 表示第二个反向引用

前面通过捕获获得的分组可以再后面引用,比如 (\w+) \1 表示两个重复的单词

assertTrue(Pattern.matches("(\\w+) \\1", "ab ab"));
assertTrue(Pattern.matches("(\\w+) \\1", "abc abc"));
assertFalse(Pattern.matches("(\\w+) \\1", "abc def"));

正则替换

StringPattern 都提供了 replaceAllreplaceFirst 方法来作正则替换,替换串中可以 $i 来获取正则匹配捕获的分组

Pattern pattern = Pattern.compile("^([a-z0-9]+)@(?:([a-z0-9.]+)[.]([a-z]{2,4}))$");
assertEquals(pattern.matcher("hatlonely@foxmail.com").replaceAll(
        "$0 $1 $2 $3"
), "hatlonely@foxmail.com hatlonely foxmail com");

assertEquals("hatlonely@foxmail.com".replaceAll(
        "^([a-z0-9]+)@(?:([a-z0-9.]+)[.]([a-z]{2,4}))$", "$0 $1 $2 $3"
), "hatlonely@foxmail.com hatlonely foxmail com");

搜索所有匹配

每次 find 调用都会匹配目标串中的一个匹配,通过多次 find 调用,可以找出目标串中所有的匹配

String str = "abab x acac y aeae";
Pattern pattern = Pattern.compile("(\\w+)\\1");
Matcher matcher = pattern.matcher(str);

List<String> li = new ArrayList<>();
while (matcher.find()) {
    li.add(matcher.group());
    assertThat(str.substring(matcher.start(), matcher.end()), equalTo(matcher.group()));
}
assertThat(li, equalTo(List.of("abab", "acac", "aeae")));

链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343