我使用以下两个正则表达式来测试带有ASP.NET验证控件的有效电子邮件表达式.我想知道从性能的角度来看哪个是更好的表达,或者有人有更好的表达.
- \w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
- ^([0-9a-zA-Z]([-\.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$
我正在努力避免在BCL团队博客上描述的"指数缓慢表达"问题.
UPDATE
根据反馈,我最终创建了一个函数来测试电子邮件是否有效:
Public Function IsValidEmail(ByVal emailString As String, Optional ByVal isRequired As Boolean = False) As Boolean
Dim emailSplit As String()
Dim isValid As Boolean = True
Dim localPart As String = String.Empty
Dim domainPart As String = String.Empty
Dim domainSplit As String()
Dim tld As String
If emailString.Length >= 80 Then
isValid = False
ElseIf emailString.Length > 0 And emailString.Length < 6 Then
'Email is too short
isValid = False
ElseIf emailString.Length > 0 Then
'Email is optional, only test value if provided
emailSplit = emailString.Split(CChar("@"))
If emailSplit.Count <> 2 Then
'Only 1 @ should exist
isValid = False
Else
localPart = emailSplit(0)
domainPart = emailSplit(1)
End If
If isValid = False OrElse domainPart.Contains(".") = False Then
'Needs at least 1 period after @
isValid = False
Else
'Test Local-Part Length and Characters
If localPart.Length > 64 OrElse ValidateString(localPart, ValidateTests.EmailLocalPartSafeChars) = False OrElse _
localPart.StartsWith(".") OrElse localPart.EndsWith(".") OrElse localPart.Contains("..") Then
isValid = False
End If
'Validate Domain Name Portion of email address
If isValid = False OrElse _
ValidateString(domainPart, ValidateTests.HostNameChars) = False OrElse _
domainPart.StartsWith("-") OrElse domainPart.StartsWith(".") OrElse domainPart.Contains("..") Then
isValid = False
Else
domainSplit = domainPart.Split(CChar("."))
tld = domainSplit(UBound(domainSplit))
' Top Level Domains must be at least two characters
If tld.Length < 2 Then
isValid = False
End If
End If
End If
Else
'If no value is passed review if required
If isRequired = True Then
isValid = False
Else
isValid = True
End If
End If
Return isValid
End Function
Run Code Online (Sandbox Code Playgroud)
笔记:
Ala*_*ore 12
如果你想知道为什么这个问题产生如此少的活动,那是因为在你开始考虑性能之前还有许多其他问题需要处理.其中最重要的是你是否应该使用正则表达式来验证电子邮件地址 - 而且你不应该达成共识.它比大多数人想象的要复杂得多,而且无论如何都可能毫无意义.
另一个问题是你的两个正则表达式在它们可以匹配的字符串类型上有很大差异.例如,第二个锚定在两端,但第一个不是; 它会匹配" >>>>foo@bar.com<<<<",因为它看起来像嵌入其中的电子邮件地址.也许框架强制正则表达式匹配整个字符串,但如果是这样的话,为什么第二个锚定?
另一个区别是第一个正则表达式\w始终使用,而第二个正则表达式用于[0-9a-zA-Z]许多地方.在大多数正则表达式中,\w除了字母和数字之外还匹配下划线,但在某些(包括.NET)中,它还匹配来自Unicode已知的每个书写系统的字母和数字.
还有很多其他的差异,但这是学术上的; 这些正则表达式都不是很好.请参阅此处以获得有关该主题的更好讨论,以及更好的正则表达式.
回到最初的问题,我没有看到这些正则表达式的性能问题.除了BCL博客条目中引用的嵌套量词反模式之外,您还应该注意正则表达式的两个或多个相邻部分可以匹配同一组字符的情况 - 例如,
([A-Za-z]+|\w+)@
Run Code Online (Sandbox Code Playgroud)
在你发布的任何一个正则表达式中没有类似的东西.由量词控制的零件总是被其他未量化的零件分解.两个正则表达式都会经历一些可避免的回溯,但有很多比性能更好的理由拒绝它们.
编辑:所以第二个正则表达式是受到灾难性的回溯; 在拍摄我的嘴之前,我应该彻底测试它.仔细看看那个正则表达式,我不明白为什么你需要在第一部分中使用外部星号:
[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*
Run Code Online (Sandbox Code Playgroud)
所有这一切都确保第一个和最后一个字符是字母数字,同时允许其间有一些额外的字符.这个版本做了同样的事情,但是当不可能匹配时它会更快地失败:
[0-9a-zA-Z][-.\w]*[0-9a-zA-Z]
Run Code Online (Sandbox Code Playgroud)
这可能足以消除回溯问题,但你也可以通过使用原子组使"@"之后的部分更有效:
(?>(?:[0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+)[a-zA-Z]{2,9}
Run Code Online (Sandbox Code Playgroud)
换句话说,如果你已经匹配所有可能的带有尾随点的域组件的子串,并且下一部分看起来不像TLD,则不要打扰回溯.你必须放弃的第一个角色是最后一个点,你知道它[a-zA-Z]{2,9}不会匹配.
我们使用此RegEx,已经在内部测试了150万个地址.它正确地识别出超过98%的我们的,但有些格式我知道它会出错.
^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$
Run Code Online (Sandbox Code Playgroud)
我们还确保数据中没有EOL字符,因为EOL可以伪造此RegEx.我们的职责:
Public Function IsValidEmail(ByVal strEmail As String) As Boolean
' Check An eMail Address To Ensure That It Is Valid
Const cValidEmail = "^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$" ' 98% Of All Valid eMail Addresses
IsValidEmail = False
' Take Care Of Blanks, Nulls & EOLs
strEmail = Replace(Replace(Trim$(strEmail & " "), vbCr, ""), vbLf, "")
' Blank eMail Is Invalid
If strEmail = "" Then Exit Function
' RegEx Test The eMail Address
Dim regEx As New System.Text.RegularExpressions.Regex(cValidEmail)
IsValidEmail = regEx.IsMatch(strEmail)
End Function
Run Code Online (Sandbox Code Playgroud)