这个RegEx函数中的内存问题是什么?

What is the memory issue in this RegEx function

本文关键字:问题 问题是 是什么 内存 RegEx 函数 这个      更新时间:2023-09-26

我正在尝试抓取电子邮件地址的网页。我几乎有它的工作,但似乎有某种巨大的内存错误,使页面冻结当我的脚本加载。

这是我的:

var bodyText = document.body.textContent.replace(/'n/g, " ").split(' '); // Location to pull our text from. In this case it's the whole body
    var r = new RegExp("[a-z0-9!#$%&'*+'/=?^_`{|}~-]+(?:'.[a-z0-9!#$%&'*+'/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?'.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])", 'i');
    function validateEmail(string) {
        return r.test(string);
    }
    var domains = [];
    var domain;
    for (var i = 0; i < bodyText.length; i++){
        domain = bodyText[i].toString();
        if (validateEmail(domain)) {
            domains.push(domain);
        }           
    }

我唯一能想到的是,我使用的电子邮件验证函数是一个32步表达式,我运行它的页面返回超过3000个部分,但我觉得这应该是可能的。

下面是一个重现错误的脚本:
var str = "help.yahoo.com/us/tutorials/cg/mail/cg_addressguard2.html"; 
var r = new RegExp("[a-z0-9!#$%&'*+'/=?^_{|}~-]+(?:'.[a-z0-9!#$%&'*+'/=?^_{|}~-]+)*@(?:[a-‌​z0-9](?:[a-z0-9-]*[a-z0-9])?'.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])", 'i'); 
console.log("before:"+(new Date())); 
console.log(r.test(str)); 
console.log("after:"+(new Date()));`

我能做些什么来克服内存问题?

stribizhev在评论中指出了解决方案:在RegExp literal语法中指定正则表达式。另一个解决方案,如sln的注释所示,是正确转义字符串字面值中的'

我不会在这个答案中解决用regex验证/匹配电子邮件地址的正确正则表达式是什么,因为它已经被重复了很多次。


为了演示导致问题的原因,让我们将传递给RegExp构造函数的字符串打印到控制台。你注意到有些'不见了吗?

[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])
               ^               ^               ^                                                 ^

上面的字符串是RegExp构造函数看到并编译的。

  • /只需要在RegExp字面量中转义(因为RegExp字面量由/分隔),并且不需要在传递给RegExp构造器的字符串中转义,因此省略不会引起任何问题。

    下面是等效的示例,展示了如何编写一个正则表达式来匹配/与RegExp字面值和RegExp构造函数:

    /'//;
    new RegExp("/");
    
  • 然而,由于'.中的'在字符串中没有被正确转义,所以它不匹配文字.,它允许匹配任何字符(除了行分隔符)。

    因此,作为完美的解决方案,正则表达式中的这些部分遭受了灾难性的回溯:

    (?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*
    (?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+
    

    由于.可以匹配任何字符,因此上面的片段退化为经典的灾难性回溯模式(A*)*。通过减少正则表达式对其严格子集的能力,您可以更清楚地看到问题:

    (?:a[a]+)*
    (?:[a](?:[a]*[a])?a)+
    

这是RegExp字面量的解决方案,它与问题中的字符串字面量中指定的相同。您已经正确地完成了RegExp文字的转义,但是在RegExp构造函数中使用它:

var r = /[a-z0-9!#$%&'*+'/=?^_`{|}~-]+(?:'.[a-z0-9!#$%&'*+'/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?'.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])/i;

对于等效的RegExp构造函数解:

var r = new RegExp("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:''.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?''.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])", "i");

不完全是你问题的答案,但你需要做的第一件事是减少你必须用"纠正"的文本部分进行测试的数量。模式。在html示例文件中,您有大约3300个文本字符串要用正则表达式进行测试。请记住,使用正则表达式是有代价的,所以删除无用的文本部分是优先考虑的:

var textParts = document.body.textContent
               .split(/'s+/) // see the note
               .filter(function(part) {
                   return part.length > 4 && part.length < 255 && part.indexOf('@') > 1; 
               });
               
alert(textParts.join("'n"));

现在您只有~50个文本部分要测试。

注意:如果你想在帐户电子邮件地址中加入双引号,你可以尝试更改:

.split(/'s+/)

.split(/(?=['s"])((?:"[^"'n'']*(?:''.[^"'n'']*)*"[^"'s]*)*)(?:'s+|$)/)

(无担保)

关于你的模式:你的模式中的错误已经被其他答案和注释指出了,但请注意,你可能可以用这个更快地获得相同的结果(相同的匹配):

/'b'w[!#-'*+'/-9=?^-~-]*(?:'.[!#-'*+'/-9=?^-~-]+)*@[a-z0-9]+(?:-[a-z0-9]+)*'.[a-z0-9]+(?:[-.][a-z0-9]+)*'b/i

下面是一个不那么严格的正则表达式的例子。

function getEmails(str) {
  var r = /'b[A-Z0-9._%+-]+@[A-Z0-9.-]+'.[A-Z]{2,4}'b/ig;
  var emails = [];
  var e = null;
  var n = 0;
  while ((e = r.exec(str)) !== null) {
    emails[n++] = e[0];
  }
  return emails;
}
function emailTest() {
   var str = document.getElementsByTagName('body')[0].innerHTML;
   var emails = getEmails(str);
   document.getElementById('found').innerHTML=emails.join("'n");
}
emailTest();
  
#found {
  color:green;
  font-weight:bold;
}
<pre id="email_test">
  test@test.test
  foo@bar.baz.test
  foo@bar.baz.longdomain
  foo-bar@foo.bar
  foo_bar99@foo.bar
  foo@foo@foo.bar
  foo$bar#33@test.test
  foo+bar-baz%99@someplace.top
</pre>
<pre id="found"></pre>

相关文章: