线上一个日志清洗服务跑到凌晨突然变慢,内存一路涨。翻代码发现有人把 Pattern.compile 写在了循环里,每处理一行就重新编译一次正则。这行代码本身没错,放错位置就要命。
Pattern.compile 在做什么
Java 里正则有两条路:直接调 String.matches 每次现编现用,或者用 Pattern.compile 预编译成 Pattern 对象,再拿 matcher 去匹配。编译正则要把表达式解析成内部状态机,开销不小。Pattern 本身线程安全,编好的对象可以放心共享,但 Matcher 不行,得每次新建。
正确的写法
private static final Pattern P =
Pattern.compile(「[0-9]{4}-[0-9]{2}-[0-9]{2}」);
Matcher m = P.matcher(text);
while (m.find()) { 处理 }把 Pattern 提成 static final 常量,类加载时编译一次,全站复用,性能差几十倍都不夸张。这里刻意用 [0-9] 而不是简写元字符,省得在两个转义层里数反斜杠。
几个真踩过的坑
- 写进循环:最典型,一行一次编译,CPU 直接拉满。
- 贪婪匹配:点星默认吃到最远,处理 HTML 时容易一口气吞掉整段,要换成懒惰模式。
- 灾难性回溯:嵌套量词加长文本,能把线程卡死,这是正则性能事故的头号来源。
- 忽略大小写:需要时加 CASE_INSENSITIVE 标志,别在正则里手动展开大小写。
怎么验证写对了
先用 IDE 的正则测试功能跑几个边界样例,尤其测空串、超长串、含特殊字符的串。上线前对热点路径的正则做一次基准测试,看单次匹配耗时。Pattern.quote 能把用户输入当纯文本处理,避免他塞进来的特殊字符被当成语法。
- 提醒:正则好用但别炫技,一个复杂表达式配上注释和测试样例,比事后排查回溯事故划算得多。