如何中断正则的执行_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > 如何中断正则的执行

如何中断正则的执行

 2014/6/17 18:37:17  deepinmind  程序员俱乐部  我要评论(0)
  • 摘要:如果你在工作经常使用正则表达式的话,你可能对灾难性回溯(catastrophicbacktracking)这个概念并不陌生,这意味着你强迫正则引擎去进行指数级的排列运算。比如说,点击这里运行下这个用例来看看它得花多长时间(大概是5到10秒):publicclassLongRunningRegexExample{publicstaticvoidmain(String[]args)throwsInterruptedException{finalPatternpattern=Pattern
  • 标签:执行 正则
如果你在工作经常使用正则表达式的话,你可能对灾难性回溯(catastrophic backtracking)这个概念并不陌生,这意味着你强迫正则引擎去进行指数级的排列运算。比如说,点击这里运行下这个用例来看看它得花多长时间(大概是5到10秒):

class="java" name="code">

public class LongRunningRegexExample
{
   public static void main(String[] args) throws InterruptedException
   {
      final Pattern pattern = Pattern.compile("(0*)*A");
      final String input = "00000000000000000000000000";

      long startTime = System.currentTimeMillis();
      Matcher matcher = pattern.matcher(input);
      matcher.find(); // runs for a long time!
      System.out.println("Regex took:" + (System.currentTimeMillis() - startTime) + "ms");
   }
}



然而只需一个很小的改动就能让它瞬间返回。为什么?引用 Dzone的Andreas Haufler在他的_如何用一个正则表达式杀掉Java程序_一文中的一句话,这个简洁的示例程序也是来自这篇文章的:


乍一看它像是一个无限循环,这其实是一个灾难性回溯。它的意思是匹配器没有在输入的结束处找到一个'A'。因此外部的检测器会后退一步,而内部的则前进一步,但还是没有发现

因此匹配器会一步步地回退,尝试尽所有的组合来查找是否存在一个匹配。它最终还是会返回的(没有匹配上),但时间复杂度是指数级的(增加一个字符运行时间会翻倍)。


那么我们该如何做才能避免这样的影响,有没有可能会遇到这么一个场景,要让Java进程运行好几天,甚至好几年的?

如果你不嫌麻烦的话,你可以在自己的正则模式中注意去避免这样的情况。但是如果你部署了一个应用,它接受一个输入作为正则表达式的话——比如说一个在线的Java正则校验器,那么你需要防止这种情况,否则就可能会遭受拒绝服务攻击。

答案非常简单,不过你需要引入一个线程,下面是我在stackoverflow上找到的一个特殊的字符序列的实现。


public class InterruptibleRegexExample
{
   public static void main(String[] args) throws InterruptedException
   {
      final Pattern pattern = Pattern.compile("(0*)*A");
      final String input = "00000000000000000000000000";

      Runnable runnable = new Runnable() {
         @Override
         public void run()
         {
            long startTime = System.currentTimeMillis();
            Matcher interruptableMatcher = pattern.matcher(new InterruptibleCharSequence(input));
            interruptableMatcher.find(); // runs for a long time!
            System.out.println("Regex took:" + (System.currentTimeMillis() - startTime) + "ms");
         }
      };

      Thread thread = new Thread(runnable);
      thread.start();

      Thread.sleep(500);
      thread.interrupt();
   }
}


这样这个正则就非常棒了,它是可中断的。

Exception in thread "Thread-2" java.lang.RuntimeException: Die! ... Why won't you DIE!
at org.ocpsoft.tutorial.regex.server.InterruptRegex$InterruptibleCharSequence.charAt(InterruptRegex.java:72)
at java.util.regex.Pattern$BmpCharProperty.match(Pattern.java:3366)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3760)
at java.util.regex.Pattern$Curly.match(Pattern.java:3744)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4168)



当然了你需要用到InterruptibleCharSequence,这多少会影响到程序的性能,不过总比干等一年要好得多:

/**
 * {@link CharSequence} that notices {@link Thread} interrupts
 * 
 * @author gojomo - http://stackoverflow.com/questions/910740/cancelling-a-long-running-regex-match
 */
private static class InterruptibleCharSequence implements CharSequence {
   CharSequence inner;

   public InterruptibleCharSequence(CharSequence inner) {
      super();
      this.inner = inner;
   }

   @Override
   public char charAt(int index) {
      if (Thread.currentThread().isInterrupted()) {
         throw new RuntimeException("Interrupted!");
      }
      return inner.charAt(index);
   }

   @Override
   public int length() {
      return inner.length();
   }

   @Override
   public CharSequence subSequence(int start, int end) {
      return new InterruptibleCharSequence(inner.subSequence(start, end));
   }

   @Override
   public String toString() {
      return inner.toString();
   }
}




原创文章转载请注明出处:http://it.deepinmind.com


英文原文链接
发表评论
用户名: 匿名