You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android平台如何实现regex.match()正则匹配操作超时终止

Android平台regex.match()操作超时中断实现方案

问题背景

  • 业务场景:应用通过IntentService爬取HTML内容,使用正则匹配完成内容解析
  • 故障表现:HTML页面格式变更时,regex.match()偶发长时间阻塞,直接导致应用ANR无响应
  • 已尝试方案:参考公开实现及Google CTS源码编写了基于自定义TimeoutCharSequence的超时工具类,核心逻辑为在charAt()方法中校验超时时间、抛出异常中断匹配,工具类代码如下:
public class RegexUtils {

    public RegexUtils() {
    }

    public void test() {
        long millis = System.currentTimeMillis();
        Matcher matcher = createMatcherWithTimeout("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "(x+x+)+y", 10000);
        try {
            Timber.d("RegexUtils: %s", (matcher.find() ? "Matches found" : "No matches found"));
        } catch (RuntimeException e) {
            Timber.w("RegexUtils: Operation timed out after " + (System.currentTimeMillis() - millis) + " milliseconds");
        }
    }

    public static Matcher createMatcherWithTimeout(String stringToMatch, String regularExpression, long timeoutMillis) {
        Pattern pattern = Pattern.compile(regularExpression);
        return createMatcherWithTimeout(stringToMatch, pattern, timeoutMillis);
    }

    public static Matcher createMatcherWithTimeout(String stringToMatch, Pattern regularExpressionPattern, long timeoutMillis) {
        if (timeoutMillis < 0) {
            return regularExpressionPattern.matcher(stringToMatch);
        }
        TimeoutCharSequence charSequence = new TimeoutCharSequence(stringToMatch, timeoutMillis);
        return regularExpressionPattern.matcher(charSequence);
    }

    private static class TimeoutCharSequence implements CharSequence {
        long expireTime = 0;
        CharSequence chars = null;
        TimeoutCharSequence(CharSequence chars, long timeout) {
            this.chars = chars;
            expireTime = System.currentTimeMillis() + timeout;
        }
        @Override
        public char charAt(int index) {
            if (System.currentTimeMillis() > expireTime) {
                throw new CharSequenceTimeoutException("TimeoutCharSequence was used after the expiration time.");
            }
            return chars.charAt(index);
        }
        @Override
        public int length() {
            return chars.length();
        }
        @Override
        public CharSequence subSequence(int start, int end) {
            return new TimeoutCharSequence(chars.subSequence(start, end), expireTime - System.currentTimeMillis());
        }
        @Override
        public String toString() {
            return chars.toString();
        }
        private static class CharSequenceTimeoutException extends RuntimeException {
            public CharSequenceTimeoutException(String message) {
                super(message);
            }
        }
    }
}
  • 方案失效原因:Android平台内置java.util.regex正则引擎底层实现会直接调用传入CharSequence的toString()方法获取完整字符串做内部处理,不会逐次调用charAt()方法遍历字符,因此埋在charAt()中的超时校验逻辑完全不会被触发,无法实现中断。

可行实现方案

方案1:独立线程池执行 + Future超时控制(兼容性最优,推荐)

核心逻辑是将正则匹配任务提交到独立的守护线程池执行,通过Future.get()的超时机制限制最长执行时长,超时直接丢弃任务,避免阻塞业务流程。

注意:Java内置正则引擎不响应线程中断信号,因此工作线程需要设置为守护线程,超时后直接丢弃任务引用即可,JVM会自动回收相关资源,不会造成残留。
参考实现代码:

import java.util.concurrent.*;
import java.util.regex.Pattern;

public class RegexTimeoutUtils {
    // 正则匹配专用单线程守护线程池,避免和其他业务任务抢占资源
    private static final ExecutorService REGEX_POOL = Executors.newSingleThreadExecutor(r -> {
        Thread worker = new Thread(r, "regex-match-worker");
        worker.setDaemon(true);
        return worker;
    });

    /**
     * 带超时的正则find操作
     * @param input 待匹配文本
     * @param pattern 预编译的正则对象
     * @param timeoutMs 最大允许执行时长,单位毫秒
     * @return 匹配到结果返回true,未匹配到返回false
     * @throws TimeoutException 执行超时时抛出
     * @throws ExecutionException 正则执行内部错误时抛出
     * @throws InterruptedException 线程被中断时抛出
     */
    public static boolean findWithTimeout(String input, Pattern pattern, long timeoutMs) throws TimeoutException, ExecutionException, InterruptedException {
        Future<Boolean> matchTask = REGEX_POOL.submit(() -> pattern.matcher(input).find());
        try {
            return matchTask.get(timeoutMs, TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            matchTask.cancel(true);
            throw e;
        }
    }
}

方案优缺点:

  • 优点:兼容所有Android版本,不需要修改现有正则表达式,改造成本低,稳定性高
  • 缺点:存在极轻微的线程调度开销,超高频调用场景需要提前评估性能影响

方案2:替换为线性时间正则引擎RE2/J

可以直接替换系统内置正则实现,使用Google开源的RE2/J正则库。该引擎采用线性时间匹配算法,从根源上避免了灾难性回溯导致的长时间阻塞问题,同时原生支持超时配置。
方案优缺点:

  • 优点:不需要额外做线程调度,性能更高,不存在正则卡死的可能性
  • 缺点:RE2/J不支持全部Java正则语法(如反向引用、部分零宽断言特性),使用前需要确认业务所用正则语法兼容

方案3:高版本直接使用官方超时API

从Android 14(API 级别 34)开始,系统内置的Matcher类新增了setTimeout(Duration duration)方法,可直接为匹配操作设置超时时间,超时会抛出MatcherTimeoutException中断执行。如果应用minSdkVersion >= 34,可以直接调用该官方API实现,不需要额外兼容。

选型建议

  • 绝大多数普通业务场景优先选择方案1,改造成本最低,兼容性最好
  • 如果业务正则语法简单、对匹配性能要求极高,可以选择方案2
  • 仅面向Android 14及以上版本的应用,直接使用官方API即可

内容的提问来源于stack exchange,提问作者rob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 14:36:16