.NET의 정규식 엔진은 리터럴 텍스트를 비교하고 일치시키는 대신 패턴 일치를 기반으로 텍스트를 처리하는 강력한 전체 기능을 갖춘 도구입니다. 대부분의 경우 패턴 일치를 신속하고 효율적으로 수행합니다. 그러나 경우에 따라 정규식 엔진이 느린 것처럼 보일 수 있습니다. 극단적인 경우 몇 시간 또는 며칠 동안 비교적 작은 입력을 처리하므로 응답을 중지하는 것처럼 보일 수도 있습니다.
이 문서에서는 정규식이 최적의 성능을 달성할 수 있도록 개발자가 채택할 수 있는 몇 가지 모범 사례를 간략하게 설명합니다.
경고
System.Text.RegularExpressions를 사용하여 신뢰할 수 없는 입력을 처리하는 경우 시간 제한을 전달합니다. 악의적인 사용자가 RegularExpressions
입력을 제공하여 서비스 거부 공격일으킬 수 있습니다.
RegularExpressions
를 사용하는 ASP.NET Core Framework API는 시간 제한을 전달합니다.
입력 원본 고려
일반적으로 정규식은 제한되거나 제한되지 않은 두 가지 유형의 입력을 허용할 수 있습니다. 제한된 입력은 알려진 소스 또는 신뢰할 수 있는 원본에서 시작되고 미리 정의된 형식을 따르는 텍스트입니다. 제한되지 않은 입력은 웹 사용자와 같은 신뢰할 수 없는 원본에서 시작되며 미리 정의되거나 예상된 형식을 따르지 않을 수 있는 텍스트입니다.
정규식 패턴은 유효한 입력과 일치하도록 작성되는 경우가 많습니다. 즉, 개발자는 일치시킬 텍스트를 검사한 다음 일치하는 정규식 패턴을 작성합니다. 그런 다음 개발자는 이 패턴을 여러 개의 유효한 입력 항목으로 테스트하여 수정 또는 추가 경과가 필요한지 여부를 결정합니다. 패턴이 유효한 것으로 추정되는 모든 입력과 일치하면 프로덕션 준비로 선언되며 릴리스된 애플리케이션에 포함될 수 있습니다. 이 방법을 사용하면 정규식 패턴이 제한된 입력을 일치시키는 데 적합합니다. 그러나 제한되지 않은 입력을 일치시키는 데 적합하지는 않습니다.
제한되지 않은 입력과 일치하려면 정규식이 세 종류의 텍스트를 효율적으로 처리해야 합니다.
- 정규식 패턴과 일치하는 텍스트입니다.
- 정규식 패턴과 일치하지 않는 텍스트입니다.
- 정규식 패턴과 거의 일치하는 텍스트입니다.
마지막 텍스트 형식은 제한된 입력을 처리하도록 작성된 정규식에 특히 문제가 됩니다. 해당 정규식이 광범위한 역추적에도 의존하는 경우 정규식 엔진은 겉보기에 무해한 텍스트를 처리하는 데 많은 시간(경우에 따라 여러 시간 또는 며칠)을 소비할 수 있습니다.
경고
다음 예제에서는 과도한 역추적이 발생하기 쉽고 유효한 전자 메일 주소를 거부할 가능성이 있는 정규식을 사용합니다. 전자 메일 유효성 검사 루틴에서는 사용하지 않아야 합니다. 전자 메일 주소의 유효성을 검사하는 정규식을 원하는 경우 방법: 문자열이 유효한 전자 메일 형식인지 확인합니다.
예를 들어 전자 메일 주소의 별칭의 유효성을 검사하기 위해 일반적으로 사용되지만 문제가 있는 정규식을 고려해 보세요. 정규식 ^[0-9A-Z]([-.\w]*[0-9A-Z])*$
은 유효한 전자 메일 주소로 간주되는 것을 처리하기 위해 작성됩니다. 유효한 전자 메일 주소는 영숫자 문자로 시작하고, 그 뒤에 올 수 있는 영숫자, 마침표, 또는 하이픈으로 구성된 0개 이상의 문자로 구성됩니다. 정규식은 영숫자 문자로 끝나야 합니다. 그러나 다음 예제와 같이 이 정규식은 유효한 입력을 쉽게 처리하지만 거의 유효한 입력을 처리할 때 성능이 비효율적입니다.
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
public class DesignExample
{
public static void Main()
{
Stopwatch sw;
string[] addresses = { "AAAAAAAAAAA@contoso.com",
"AAAAAAAAAAaaaaaaaaaa!@contoso.com" };
// The following regular expression should not actually be used to
// validate an email address.
string pattern = @"^[0-9A-Z]([-.\w]*[0-9A-Z])*$";
string input;
foreach (var address in addresses)
{
string mailBox = address.Substring(0, address.IndexOf("@"));
int index = 0;
for (int ctr = mailBox.Length - 1; ctr >= 0; ctr--)
{
index++;
input = mailBox.Substring(ctr, index);
sw = Stopwatch.StartNew();
Match m = Regex.Match(input, pattern, RegexOptions.IgnoreCase);
sw.Stop();
if (m.Success)
Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
index, m.Value, sw.Elapsed);
else
Console.WriteLine("{0,2}. Failed '{1,25}' in {2}",
index, input, sw.Elapsed);
}
Console.WriteLine();
}
}
}
// The example displays output similar to the following:
// 1. Matched ' A' in 00:00:00.0007122
// 2. Matched ' AA' in 00:00:00.0000282
// 3. Matched ' AAA' in 00:00:00.0000042
// 4. Matched ' AAAA' in 00:00:00.0000038
// 5. Matched ' AAAAA' in 00:00:00.0000042
// 6. Matched ' AAAAAA' in 00:00:00.0000042
// 7. Matched ' AAAAAAA' in 00:00:00.0000042
// 8. Matched ' AAAAAAAA' in 00:00:00.0000087
// 9. Matched ' AAAAAAAAA' in 00:00:00.0000045
// 10. Matched ' AAAAAAAAAA' in 00:00:00.0000045
// 11. Matched ' AAAAAAAAAAA' in 00:00:00.0000045
//
// 1. Failed ' !' in 00:00:00.0000447
// 2. Failed ' a!' in 00:00:00.0000071
// 3. Failed ' aa!' in 00:00:00.0000071
// 4. Failed ' aaa!' in 00:00:00.0000061
// 5. Failed ' aaaa!' in 00:00:00.0000081
// 6. Failed ' aaaaa!' in 00:00:00.0000126
// 7. Failed ' aaaaaa!' in 00:00:00.0000359
// 8. Failed ' aaaaaaa!' in 00:00:00.0000414
// 9. Failed ' aaaaaaaa!' in 00:00:00.0000758
// 10. Failed ' aaaaaaaaa!' in 00:00:00.0001462
// 11. Failed ' aaaaaaaaaa!' in 00:00:00.0002885
// 12. Failed ' Aaaaaaaaaaa!' in 00:00:00.0005780
// 13. Failed ' AAaaaaaaaaaa!' in 00:00:00.0011628
// 14. Failed ' AAAaaaaaaaaaa!' in 00:00:00.0022851
// 15. Failed ' AAAAaaaaaaaaaa!' in 00:00:00.0045864
// 16. Failed ' AAAAAaaaaaaaaaa!' in 00:00:00.0093168
// 17. Failed ' AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
// 18. Failed ' AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
// 19. Failed ' AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
// 20. Failed ' AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
// 21. Failed ' AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372
Imports System.Diagnostics
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim sw As Stopwatch
Dim addresses() As String = {"AAAAAAAAAAA@contoso.com",
"AAAAAAAAAAaaaaaaaaaa!@contoso.com"}
' The following regular expression should not actually be used to
' validate an email address.
Dim pattern As String = "^[0-9A-Z]([-.\w]*[0-9A-Z])*$"
Dim input As String
For Each address In addresses
Dim mailBox As String = address.Substring(0, address.IndexOf("@"))
Dim index As Integer = 0
For ctr As Integer = mailBox.Length - 1 To 0 Step -1
index += 1
input = mailBox.Substring(ctr, index)
sw = Stopwatch.StartNew()
Dim m As Match = Regex.Match(input, pattern, RegexOptions.IgnoreCase)
sw.Stop()
if m.Success Then
Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
index, m.Value, sw.Elapsed)
Else
Console.WriteLine("{0,2}. Failed '{1,25}' in {2}",
index, input, sw.Elapsed)
End If
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays output similar to the following:
' 1. Matched ' A' in 00:00:00.0007122
' 2. Matched ' AA' in 00:00:00.0000282
' 3. Matched ' AAA' in 00:00:00.0000042
' 4. Matched ' AAAA' in 00:00:00.0000038
' 5. Matched ' AAAAA' in 00:00:00.0000042
' 6. Matched ' AAAAAA' in 00:00:00.0000042
' 7. Matched ' AAAAAAA' in 00:00:00.0000042
' 8. Matched ' AAAAAAAA' in 00:00:00.0000087
' 9. Matched ' AAAAAAAAA' in 00:00:00.0000045
' 10. Matched ' AAAAAAAAAA' in 00:00:00.0000045
' 11. Matched ' AAAAAAAAAAA' in 00:00:00.0000045
'
' 1. Failed ' !' in 00:00:00.0000447
' 2. Failed ' a!' in 00:00:00.0000071
' 3. Failed ' aa!' in 00:00:00.0000071
' 4. Failed ' aaa!' in 00:00:00.0000061
' 5. Failed ' aaaa!' in 00:00:00.0000081
' 6. Failed ' aaaaa!' in 00:00:00.0000126
' 7. Failed ' aaaaaa!' in 00:00:00.0000359
' 8. Failed ' aaaaaaa!' in 00:00:00.0000414
' 9. Failed ' aaaaaaaa!' in 00:00:00.0000758
' 10. Failed ' aaaaaaaaa!' in 00:00:00.0001462
' 11. Failed ' aaaaaaaaaa!' in 00:00:00.0002885
' 12. Failed ' Aaaaaaaaaaa!' in 00:00:00.0005780
' 13. Failed ' AAaaaaaaaaaa!' in 00:00:00.0011628
' 14. Failed ' AAAaaaaaaaaaa!' in 00:00:00.0022851
' 15. Failed ' AAAAaaaaaaaaaa!' in 00:00:00.0045864
' 16. Failed ' AAAAAaaaaaaaaaa!' in 00:00:00.0093168
' 17. Failed ' AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
' 18. Failed ' AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
' 19. Failed ' AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
' 20. Failed ' AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
' 21. Failed ' AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372
앞의 예제의 출력에서 알 수 있듯이 정규식 엔진은 길이에 관계없이 거의 동일한 시간 간격으로 유효한 이메일 별칭을 처리합니다. 반면에 거의 유효한 전자 메일 주소에 5자 이상이 있는 경우 처리 시간은 문자열의 각 추가 문자에 대해 약 두 배가 됩니다. 따라서 거의 유효한 28자 문자열은 처리하는 데 1시간이 걸리며 거의 유효한 33자 문자열은 처리하는 데 거의 하루가 걸립니다.
이 정규식은 일치시킬 입력 형식을 고려하여 개발되었으므로 패턴과 일치하지 않는 입력을 고려하지 못합니다. 이러한 간과는 정규식 패턴과 거의 일치하는 제한 없는 입력이 성능 저하로 이어질 수 있게 합니다.
이 문제를 해결하려면 다음을 수행할 수 있습니다.
패턴을 개발할 때 특히 정규식이 제한되지 않은 입력을 처리하도록 설계된 경우 역추적이 정규식 엔진의 성능에 미치는 영향을 고려해야 합니다. 자세한 내용은 역추적 관리 섹션을 참조하세요.
잘못되고 거의 유효하지 않으며 유효한 입력을 사용하여 정규식을 철저히 테스트합니다. Rex를 사용하여 특정 정규식에 대한 입력을 임의로 생성할 수 있습니다. Rex 는 Microsoft Research의 정규식 탐색 도구입니다.
개체 인스턴스화를 적절하게 처리
의 중심에 . NET의 정규식 개체 모델은 정규식 엔진을 나타내는 클래스입니다 System.Text.RegularExpressions.Regex . 정규식 성능에 영향을 주는 가장 큰 요인은 엔진이 사용되는 방식 Regex 인 경우가 많습니다. 정규식을 정의하려면 정규식 엔진을 정규식 패턴과 긴밀하게 결합해야 합니다. 이러한 결합 프로세스는 생성자에 정규식 패턴을 전달하여 개체를 Regex 인스턴스화하거나 분석할 정규식 패턴과 문자열을 전달하여 정적 메서드를 호출하는 작업이 포함되든 관계없이 비용이 많이 듭니다.
비고
해석되고 컴파일된 정규식을 사용하는 성능에 대한 자세한 내용은 블로그 게시물 정규식 성능 최적화, 파트 II: 역추적 담당을 참조하세요.
정규식 엔진을 특정 정규식 패턴과 결합한 다음 엔진을 사용하여 여러 가지 방법으로 텍스트를 일치시킬 수 있습니다.
와 같은 Regex.Match(String, String)정적 패턴 일치 메서드를 호출할 수 있습니다. 이 메서드는 정규식 개체의 인스턴스화가 필요하지 않습니다.
Regex 객체를 인스턴스화하고 정규식 엔진을 정규식 패턴에 바인딩하는 기본 메서드인 해석된 정규식의 인스턴스 패턴 매칭 메서드를 호출할 수 있습니다. Regex 개체가
options
인수를 포함하지 않고 Compiled 플래그와 함께 인스턴스화될 때 발생합니다.개체를 Regex 인스턴스화하고 소스에서 생성된 정규식의 인스턴스 패턴 일치 메서드를 호출할 수 있습니다. 대부분의 경우 이 기술을 사용하는 것이 좋습니다. 이렇게 하려면 GeneratedRegexAttribute 특성을
Regex
을 반환하는 partial 메서드에 배치합니다.개체를 Regex 인스턴스화하고 컴파일된 정규식의 인스턴스 패턴 일치 메서드를 호출할 수 있습니다. 정규식 객체는 Regex 플래그가 포함된
options
인수를 사용하여 Compiled 객체를 인스턴스화할 때 컴파일된 패턴을 나타냅니다.
정규식 일치 메서드를 호출하는 특정 방법은 애플리케이션의 성능에 영향을 줄 수 있습니다. 다음 섹션에서는 정적 메서드 호출, 소스 생성 정규식, 해석된 정규식 및 컴파일된 정규식을 사용하여 애플리케이션의 성능을 향상시키는 경우에 대해 설명합니다.
중요합니다
메서드 호출 형식(정적, 해석됨, 소스 생성, 컴파일됨)은 메서드 호출에서 동일한 정규식을 반복적으로 사용하거나 애플리케이션에서 정규식 개체를 광범위하게 사용하는 경우 성능에 영향을 줍니다.
정적 정규식
정규식 개체를 동일한 정규식으로 반복적으로 인스턴스화하는 대신 정적 정규식 메서드를 사용하는 것이 좋습니다. 정규식 개체에서 사용되는 정규식 패턴과 달리 정적 메서드 호출에 사용되는 패턴의 작업 코드(opcodes) 또는 컴파일된 CIL(공용 중간 언어)은 정규식 엔진에 의해 내부적으로 캐시됩니다.
예를 들어 이벤트 처리기는 사용자 입력의 유효성을 검사하기 위해 다른 메서드를 자주 호출합니다. 이 예제는 다음 코드에 반영되며 Button , 컨트롤의 Click 이벤트를 사용하여 사용자가 통화 기호를 입력한 후 소수 자릿수를 하나 이상 입력했는지 여부를 확인하는 메서드 IsValidCurrency
를 호출합니다.
public void OKButton_Click(object sender, EventArgs e)
{
if (! String.IsNullOrEmpty(sourceCurrency.Text))
if (RegexLib.IsValidCurrency(sourceCurrency.Text))
PerformConversion();
else
status.Text = "The source currency value is invalid.";
}
Public Sub OKButton_Click(sender As Object, e As EventArgs) _
Handles OKButton.Click
If Not String.IsNullOrEmpty(sourceCurrency.Text) Then
If RegexLib.IsValidCurrency(sourceCurrency.Text) Then
PerformConversion()
Else
status.Text = "The source currency value is invalid."
End If
End If
End Sub
메서드의 IsValidCurrency
비효율적인 구현은 다음 예제에 나와 있습니다.
비고
각 메서드 호출은 동일한 패턴으로 Regex 개체를 재인스턴스화합니다. 즉, 메서드가 호출될 때마다 정규식 패턴을 다시 컴파일해야 합니다.
using System;
using System.Text.RegularExpressions;
public class RegexLib
{
public static bool IsValidCurrency(string currencyValue)
{
string pattern = @"\p{Sc}+\s*\d+";
Regex currencyRegex = new Regex(pattern);
return currencyRegex.IsMatch(currencyValue);
}
}
Imports System.Text.RegularExpressions
Public Module RegexLib
Public Function IsValidCurrency(currencyValue As String) As Boolean
Dim pattern As String = "\p{Sc}+\s*\d+"
Dim currencyRegex As New Regex(pattern)
Return currencyRegex.IsMatch(currencyValue)
End Function
End Module
위의 비효율적인 코드를 정적 Regex.IsMatch(String, String) 메서드 호출로 바꿔야 합니다. 이 방법을 사용하면 패턴 일치 메서드를 호출할 때마다 개체를 인스턴스화 Regex 할 필요가 없으며 정규식 엔진이 캐시에서 컴파일된 버전의 정규식을 검색할 수 있습니다.
using System;
using System.Text.RegularExpressions;
public class RegexLib2
{
public static bool IsValidCurrency(string currencyValue)
{
string pattern = @"\p{Sc}+\s*\d+";
return Regex.IsMatch(currencyValue, pattern);
}
}
Imports System.Text.RegularExpressions
Public Module RegexLib
Public Function IsValidCurrency(currencyValue As String) As Boolean
Dim pattern As String = "\p{Sc}+\s*\d+"
Return Regex.IsMatch(currencyValue, pattern)
End Function
End Module
기본적으로 가장 최근에 사용한 최근 15개의 정적 정규식 패턴이 캐시됩니다. 더 많은 수의 캐시된 정적 정규식이 필요한 애플리케이션의 경우 속성을 설정 Regex.CacheSize 하여 캐시 크기를 조정할 수 있습니다.
이 예제에서 사용되는 정규식 \p{Sc}+\s*\d+
은 입력 문자열에 통화 기호와 소수 자릿수가 하나 이상 있음을 확인합니다. 패턴은 다음 표와 같이 정의됩니다.
패턴 | 설명 |
---|---|
\p{Sc}+ |
유니코드 기호, 통화 범주에 있는 하나 이상의 문자와 일치합니다. |
\s* |
0개 이상의 공백 문자와 일치합니다. |
\d+ |
하나 이상의 10진수와 일치합니다. |
해석된 정규식과 소스 생성 및 컴파일된 정규식 비교
옵션의 사양 Compiled 을 통해 정규식 엔진에 바인딩되지 않은 정규식 패턴이 해석됩니다. 정규식 개체가 인스턴스화되면 정규식 엔진은 정규식을 작업 코드 집합으로 변환합니다. 인스턴스 메서드가 호출되면 작업 코드가 CIL로 변환되고 JIT 컴파일러에서 실행됩니다. 마찬가지로 정적 정규식 메서드가 호출되고 정규식을 캐시에서 찾을 수 없는 경우 정규식 엔진은 정규식을 작업 코드 집합으로 변환하고 캐시에 저장합니다. 그런 다음 JIT 컴파일러가 실행할 수 있도록 이러한 작업 코드를 CIL로 변환합니다. 해석된 정규식은 실행 시간이 느려지는 대가로 시작 시간을 줄입니다. 이 프로세스 때문에 정규식을 적은 수의 메서드 호출에서 사용하거나 정규식 메서드에 대한 정확한 호출 수를 알 수 없지만 작을 것으로 예상되는 경우에 가장 적합합니다. 메서드 호출 수가 증가함에 따라 시작 시간 단축으로 인한 성능 이점이 실행 속도가 느려지면서 상쇄됩니다.
옵션의 사양 Compiled 을 통해 정규식 엔진에 바인딩된 정규식 패턴이 컴파일됩니다. 따라서 정규식 개체가 인스턴스화되거나 정적 정규식 메서드가 호출되고 캐시에서 정규식을 찾을 수 없는 경우 정규식 엔진은 정규식을 중간 작업 코드 집합으로 변환합니다. 그런 다음 이러한 코드는 CIL로 변환됩니다. 메서드가 호출되면 JIT 컴파일러는 CIL을 실행합니다. 해석된 정규식과 달리 컴파일된 정규식은 시작 시간을 늘리지만 개별 패턴 일치 메서드를 더 빠르게 실행합니다. 따라서 정규식 컴파일로 인한 성능 이점은 호출된 정규식 메서드 수에 비례하여 증가합니다.
Regex
반환 메서드에 GeneratedRegexAttribute 특성을 부여하여 정규식 엔진에 바인딩된 정규 표현식 패턴은 소스 생성됩니다. 컴파일러에 연결되는 소스 생성기는 CIL에서 Regex
가 내보내는 것과 유사한 논리를 사용하여 C# 코드로 사용자 지정 RegexOptions.Compiled
파생 구현을 내보낸다.
RegexOptions.Compiled
의 모든 처리량 성능 이점(실제로는 그 이상)과 Regex.CompileToAssembly
의 시작 이점을 얻으면서도 CompileToAssembly
의 복잡성은 없습니다. 내보내는 원본은 프로젝트의 일부이므로 쉽게 보고 디버그할 수도 있습니다.
요약하면 다음을 수행하는 것이 좋습니다.
- 정규식 메서드를 특정 정규식과 함께 비교적 드물게 호출할 때 해석된 정규식을 사용합니다.
- C#에서 컴파일 시간에 알려진 인수와 함께 사용하고 특정 정규식을 비교적 자주 사용하는 경우
Regex
정규식을 사용합니다. - 특정 정규식을 비교적 자주 사용하여 정규식 메서드를 호출하고 .NET 6 또는 이전 버전을 사용하는 경우 컴파일된 정규식을 사용합니다.
해석된 정규식의 느린 실행 속도가 시작 시간 단축으로 인한 이익보다 더 큰 정확한 임계값을 결정하기는 어렵습니다. 또한 소스 생성 또는 컴파일된 정규식의 느린 시작 시간이 더 빠른 실행 속도의 이득보다 더 큰 임계값을 결정하기도 어렵습니다. 임계값은 정규식의 복잡성 및 처리되는 특정 데이터를 포함하여 다양한 요인에 따라 달라집니다. 특정 애플리케이션 시나리오에 가장 적합한 성능을 제공하는 정규식을 결정하려면 클래스를 Stopwatch 사용하여 실행 시간을 비교할 수 있습니다.
다음 예제에서는 처음 10개의 문장을 읽을 때와 William D. Guthrie의 Magna Carta 및 기타 주소 텍스트에서 모든 문장을 읽을 때 컴파일, 소스 생성 및 해석된 정규식의 성능을 비교합니다. 예제의 출력에서 알 수 있듯이 정규식 일치 메서드에 대해 10개만 호출하는 경우 해석되거나 소스에서 생성된 정규식은 컴파일된 정규식보다 더 나은 성능을 제공합니다. 그러나 컴파일된 정규식은 많은 수의 호출(이 경우 13,000개 이상)이 수행될 때 더 나은 성능을 제공합니다.
const string Pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";
static readonly HttpClient s_client = new();
[GeneratedRegex(Pattern, RegexOptions.Singleline)]
private static partial Regex GeneratedRegex();
public async static Task RunIt()
{
Stopwatch sw;
Match match;
int ctr;
string text =
await s_client.GetStringAsync("https://www.gutenberg.org/cache/epub/64197/pg64197.txt");
// Read first ten sentences with interpreted regex.
Console.WriteLine("10 Sentences with Interpreted Regex:");
sw = Stopwatch.StartNew();
Regex int10 = new(Pattern, RegexOptions.Singleline);
match = int10.Match(text);
for (ctr = 0; ctr <= 9; ctr++)
{
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine($" {ctr} matches in {sw.Elapsed}");
// Read first ten sentences with compiled regex.
Console.WriteLine("10 Sentences with Compiled Regex:");
sw = Stopwatch.StartNew();
Regex comp10 = new Regex(Pattern,
RegexOptions.Singleline | RegexOptions.Compiled);
match = comp10.Match(text);
for (ctr = 0; ctr <= 9; ctr++)
{
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine($" {ctr} matches in {sw.Elapsed}");
// Read first ten sentences with source-generated regex.
Console.WriteLine("10 Sentences with Source-generated Regex:");
sw = Stopwatch.StartNew();
match = GeneratedRegex().Match(text);
for (ctr = 0; ctr <= 9; ctr++)
{
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine($" {ctr} matches in {sw.Elapsed}");
// Read all sentences with interpreted regex.
Console.WriteLine("All Sentences with Interpreted Regex:");
sw = Stopwatch.StartNew();
Regex intAll = new(Pattern, RegexOptions.Singleline);
match = intAll.Match(text);
int matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine($" {matches:N0} matches in {sw.Elapsed}");
// Read all sentences with compiled regex.
Console.WriteLine("All Sentences with Compiled Regex:");
sw = Stopwatch.StartNew();
Regex compAll = new(Pattern,
RegexOptions.Singleline | RegexOptions.Compiled);
match = compAll.Match(text);
matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine($" {matches:N0} matches in {sw.Elapsed}");
// Read all sentences with source-generated regex.
Console.WriteLine("All Sentences with Source-generated Regex:");
sw = Stopwatch.StartNew();
match = GeneratedRegex().Match(text);
matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine($" {matches:N0} matches in {sw.Elapsed}");
return;
}
/* The example displays output similar to the following:
10 Sentences with Interpreted Regex:
10 matches in 00:00:00.0104920
10 Sentences with Compiled Regex:
10 matches in 00:00:00.0234604
10 Sentences with Source-generated Regex:
10 matches in 00:00:00.0060982
All Sentences with Interpreted Regex:
3,427 matches in 00:00:00.1745455
All Sentences with Compiled Regex:
3,427 matches in 00:00:00.0575488
All Sentences with Source-generated Regex:
3,427 matches in 00:00:00.2698670
*/
예제 \b(\w+((\r?\n)|,?\s))*\w+[.?:;!]
에서 사용되는 정규식 패턴은 다음 표와 같이 정의됩니다.
패턴 | 설명 |
---|---|
\b |
단어 경계에서부터 일치를 시작합니다. |
\w+ |
하나 이상의 단어 문자를 찾습니다. |
(\r?\n)|,?\s) |
줄바꿈 문자 앞에 0개 또는 1개의 캐리지 리턴을, 공백 문자 앞에 0개 또는 1개의 쉼표를 일치시킵니다. |
(\w+((\r?\n)|,?\s))* |
하나 이상의 단어 문자가 0개 이상 연속하여 일치하며, 이는 0개 또는 1개의 캐리지 리턴과 줄 바꿈 문자 뒤에 오거나, 0개 또는 1개의 쉼표 뒤에 공백 문자가 오는 경우입니다. |
\w+ |
하나 이상의 단어 문자를 찾습니다. |
[.?:;!] |
마침표, 물음표, 콜론, 세미콜론 또는 느낌표와 일치합니다. |
역추적을 주도하십시오
일반적으로 정규식 엔진은 선형 진행을 사용하여 입력 문자열을 이동하고 정규식 패턴과 비교합니다. 그러나 정규식 패턴에 사용되는 미정량자(예: *
, +
, ?
)가 사용될 때, 정규식 엔진은 일부 성공적인 부분 일치를 포기하고 전체 패턴의 성공적인 일치를 찾기 위해 이전에 저장된 상태로 돌아갈 수 있습니다. 이 프로세스를 역추적이라고 합니다.
팁 (조언)
역추적에 대한 자세한 내용은 정규식 동작 및 역추적에 대한 세부 정보를 참조하세요. 역추적에 대한 자세한 내용은 .NET 7의 정규식 개선 사항 및 정규식 성능 최적화 블로그 게시물을 참조하세요.
역추적을 지원하면 정규식의 성능과 유연성이 지원됩니다. 또한 정규식 개발자의 손에 정규식 엔진의 작업을 제어할 책임이 있습니다. 개발자는 종종 이러한 책임을 인식하지 못하기 때문에 역추적을 오용하거나 과도한 역추적에 의존하는 것이 정규식 성능 저하에서 가장 중요한 역할을 하는 경우가 많습니다. 최악의 시나리오에서는 입력 문자열의 각 추가 문자에 대해 실행 시간이 두 배가 될 수 있습니다. 실제로 역추적을 과도하게 사용하면 입력이 정규식 패턴과 거의 일치하는 경우 무한 루프에 해당하는 프로그래밍 방식으로 쉽게 만들 수 있습니다. 정규식 엔진은 비교적 짧은 입력 문자열을 처리하는 데 몇 시간 또는 며칠이 걸릴 수 있습니다.
애플리케이션은 역추적이 필수적이지 않더라도 이를 사용함으로 인해 성능 저하를 겪는 경우가 많습니다. 예를 들어 정규식 \b\p{Lu}\w*\b
은 다음 표와 같이 대문자로 시작하는 모든 단어와 일치합니다.
패턴 | 설명 |
---|---|
\b |
단어 경계에서부터 일치를 시작합니다. |
\p{Lu} |
대문자와 일치합니다. |
\w* |
단어 구성 문자(공백 없는 글자) 0개 이상을 찾습니다. |
\b |
단어 경계에서 경기를 종료합니다. |
단어 경계는 단어 문자의 하위 집합과 동일하지 않으므로 단어 문자를 일치시킬 때 정규식 엔진이 단어 경계를 넘을 가능성은 없습니다. 따라서 이 정규식의 경우 역추적은 일치 항목의 전체적인 성공에 영향을 줄 수 없습니다. 정규식 엔진이 단어 문자의 성공적인 예비 일치에 대해 상태를 저장해야 하기 때문에 성능이 저하되는 것일 뿐입니다.
역추적이 필요하지 않다고 판단되면 다음과 같은 몇 가지 방법으로 역추적을 사용하지 않도록 설정할 수 있습니다.
RegexOptions.NonBacktracking 옵션을 설정하면 (.NET 7에서 도입된 기능) 자세한 내용은 비백트래킹 모드참조하세요.
원자성 그룹이라고 하는 언어 요소를 사용합니다
(?>subexpression)
. 다음 예제에서는 두 정규식을 사용하여 입력 문자열을 구문 분석합니다. 첫 번째\b\p{Lu}\w*\b
는 역추적에 의존합니다. 두 번째\b\p{Lu}(?>\w*)\b
는 역추적을 사용하지 않도록 설정합니다. 예제의 출력과 같이 둘 다 동일한 결과를 생성합니다.using System; using System.Text.RegularExpressions; public class BackTrack2Example { public static void Main() { string input = "This this word Sentence name Capital"; string pattern = @"\b\p{Lu}\w*\b"; foreach (Match match in Regex.Matches(input, pattern)) Console.WriteLine(match.Value); Console.WriteLine(); pattern = @"\b\p{Lu}(?>\w*)\b"; foreach (Match match in Regex.Matches(input, pattern)) Console.WriteLine(match.Value); } } // The example displays the following output: // This // Sentence // Capital // // This // Sentence // Capital
Imports System.Text.RegularExpressions Module Example Public Sub Main() Dim input As String = "This this word Sentence name Capital" Dim pattern As String = "\b\p{Lu}\w*\b" For Each match As Match In Regex.Matches(input, pattern) Console.WriteLine(match.Value) Next Console.WriteLine() pattern = "\b\p{Lu}(?>\w*)\b" For Each match As Match In Regex.Matches(input, pattern) Console.WriteLine(match.Value) Next End Sub End Module ' The example displays the following output: ' This ' Sentence ' Capital ' ' This ' Sentence ' Capital
대부분의 경우 정규식 패턴을 입력 텍스트와 일치시키는 데 역추적이 필요합니다. 그러나 과도한 역추적은 성능을 심각하게 저하시키고 애플리케이션이 응답을 중지했다는 인상을 줄 수 있습니다. 특히 이 문제는 수량자가 중첩되고 외부 하위 식과 일치하는 텍스트가 내부 하위 식과 일치하는 텍스트의 하위 집합일 때 발생합니다.
경고
과도한 역추적을 방지하는 것 외에도 시간 제한 기능을 사용하여 과도한 역추적이 정규식 성능을 심각하게 저하하지 않도록 해야 합니다. 자세한 내용은 제한 시간 값 사용 섹션을 참조하세요 .
예를 들어 정규식 패턴 ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$
은 하나 이상의 영숫자 문자로 구성된 부품 번호와 일치하기 위한 것입니다. 모든 추가 문자는 영숫자 문자, 하이픈, 밑줄 또는 마침표로 구성될 수 있지만 마지막 문자는 영숫자여야 합니다. 달러 기호는 부품 번호를 종료합니다. 경우에 따라 수량자가 중첩되고 하위 [0-9A-Z]
식이 하위 [-.\w]*
식의 하위 집합이기 때문에 이 정규식 패턴의 성능이 저하될 수 있습니다.
이러한 경우 중첩된 수량자를 제거하고 외부 하위 식이 너비가 0인 lookahead 또는 lookbehind 어설션으로 바꿔 정규식 성능을 최적화할 수 있습니다. Lookahead 및 lookbehind 어설션은 앵커입니다. 입력 문자열에서 포인터를 이동하지 않고 앞이나 뒤로 이동하여 지정된 조건이 충족되는지 확인합니다. 예를 들어 부품 번호 정규식을 .로 ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$
다시 작성할 수 있습니다. 이 정규식 패턴은 다음 표와 같이 정의됩니다.
패턴 | 설명 |
---|---|
^ |
입력 문자열의 시작 부분에서 일치 항목 찾기를 시작합니다. |
[0-9A-Z] |
영숫자 문자를 매치하세요. 부품 번호는 적어도 이 문자로 구성되어야 합니다. |
[-.\w]* |
단어 문자, 하이픈 또는 마침표가 0개 이상 나타나는 것을 찾습니다. |
\$ |
달러 기호를 일치시킵니다. |
(?<=[0-9A-Z]) |
달러 기호의 뒤쪽을 찾아 이전 문자가 영숫자인지 확인합니다. |
$ |
입력 문자열의 끝에서 일치 항목을 종료합니다. |
다음 예제에서는 가능한 부품 번호가 포함된 배열과 일치하도록 이 정규식을 사용하는 방법을 보여 줍니다.
using System;
using System.Text.RegularExpressions;
public class BackTrack4Example
{
public static void Main()
{
string pattern = @"^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$";
string[] partNos = { "A1C$", "A4", "A4$", "A1603D$", "A1603D#" };
foreach (var input in partNos)
{
Match match = Regex.Match(input, pattern);
if (match.Success)
Console.WriteLine(match.Value);
else
Console.WriteLine("Match not found.");
}
}
}
// The example displays the following output:
// A1C$
// Match not found.
// A4$
// A1603D$
// Match not found.
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim pattern As String = "^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$"
Dim partNos() As String = {"A1C$", "A4", "A4$", "A1603D$",
"A1603D#"}
For Each input As String In partNos
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine(match.Value)
Else
Console.WriteLine("Match not found.")
End If
Next
End Sub
End Module
' The example displays the following output:
' A1C$
' Match not found.
' A4$
' A1603D$
' Match not found.
.NET의 정규식 언어에는 중첩된 수량자를 제거하는 데 사용할 수 있는 다음 언어 요소가 포함되어 있습니다. 자세한 내용은 그룹화 구문을 참조하세요.
Language 요소 | 설명 |
---|---|
(?=
subexpression
)
|
너비가 0인 긍정 전방 탐색. 현재 위치보다 앞을 내다보며 입력 문자열과 일치하는지 여부를 subexpression 확인합니다. |
(?!
subexpression
)
|
너비가 0인 부정 전방 탐색입니다. 현재 위치 앞을 살펴보고 subexpression 이(가) 입력 문자열과 일치하지 않는지 확인합니다. |
(?<=
subexpression
)
|
너비가 0인 긍정적 후방 탐색입니다. 현재 위치 뒤를 확인하여 입력 문자열과 일치하는지 여부를 subexpression 확인합니다. |
(?<!
subexpression
)
|
너비가 0인 역방향 부정 탐색입니다. 현재 위치 뒤를 확인하여 입력 문자열과 일치하지 않는지 여부를 subexpression 확인합니다. |
제한 시간 값 사용
정규식이 정규식 패턴과 거의 일치하는 입력을 처리하는 경우 과도한 역추적에 의존하여 성능에 상당한 영향을 줄 수 있습니다. 거의 일치하는 입력에 대해 역추적 및 테스트 정규식을 사용하는 것을 신중하게 고려하는 것 외에도, 발생하는 경우 과도한 역추적의 영향을 최소화하기 위해 항상 시간 제한 값을 설정해야 합니다.
정규식 제한 시간 간격은 정규식 엔진이 시간 초과되기 전에 단일 일치 항목을 찾는 기간을 정의합니다. 정규식 패턴 및 입력 텍스트에 따라 실행 시간이 지정된 제한 시간 간격을 초과할 수 있지만 지정된 제한 시간 간격보다 역추적하는 데 더 많은 시간이 소요되지는 않습니다. 기본 시간 초과 간격은 Regex.InfiniteMatchTimeout로, 이는 정규식이 시간 초과되지 않음을 의미합니다. 이 기본값을 재정의하고 다음과 같이 시간 초과 간격을 설정할 수 있습니다.
개체를 Regex(String, RegexOptions, TimeSpan) 인스턴스화 Regex 할 때 시간 제한 값을 제공하려면 생성자를 호출합니다.
매개 변수를 포함하는 정적 패턴 일치 메서드(예: Regex.Match(String, String, RegexOptions, TimeSpan) 또는 Regex.Replace(String, String, String, RegexOptions, TimeSpan))를 호출합니다
matchTimeout
.와 같은
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromMilliseconds(100));
코드를 사용하여 프로세스 전체 또는 앱 도메인 전체 값을 설정합니다.
제한 시간 간격을 정의했고 해당 간격의 끝에 일치하는 항목을 찾을 수 없는 경우 정규식 메서드는 예외를 RegexMatchTimeoutException throw합니다. 예외 처리기에서 더 긴 시간 제한 간격으로 일치를 다시 시도하거나, 일치 시도를 중단하거나, 일치 항목이 없다고 가정하거나, 일치 시도를 포기하고, 향후 분석을 위해 예외 정보를 기록하도록 선택할 수 있습니다.
다음 예제에서는 제한 시간 간격이 350밀리초인 정규식을 인스턴스화하여 텍스트 문서에서 단어의 단어 수와 평균 문자 수를 계산하는 메서드를 정의 GetWordData
합니다. 일치 작업이 타임아웃되면 시간 제한 간격이 350 밀리초 증가하고, Regex 개체가 재생성됩니다. 새 제한 시간 간격이 1초를 초과하는 경우, 메서드는 예외를 호출자에게 다시 던집니다.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
public class TimeoutExample
{
public static void Main()
{
RegexUtilities util = new RegexUtilities();
string title = "Doyle - The Hound of the Baskervilles.txt";
try
{
var info = util.GetWordData(title);
Console.WriteLine($"Words: {info.Item1:N0}");
Console.WriteLine($"Average Word Length: {info.Item2:N2} characters");
}
catch (IOException e)
{
Console.WriteLine($"IOException reading file '{title}'");
Console.WriteLine(e.Message);
}
catch (RegexMatchTimeoutException e)
{
Console.WriteLine($"The operation timed out after {e.MatchTimeout.TotalMilliseconds:N0} milliseconds");
}
}
}
public class RegexUtilities
{
public Tuple<int, double> GetWordData(string filename)
{
const int MAX_TIMEOUT = 1000; // Maximum timeout interval in milliseconds.
const int INCREMENT = 350; // Milliseconds increment of timeout.
List<string> exclusions = new List<string>(new string[] { "a", "an", "the" });
int[] wordLengths = new int[29]; // Allocate an array of more than ample size.
string input = null;
StreamReader sr = null;
try
{
sr = new StreamReader(filename);
input = sr.ReadToEnd();
}
catch (FileNotFoundException e)
{
string msg = String.Format("Unable to find the file '{0}'", filename);
throw new IOException(msg, e);
}
catch (IOException e)
{
throw new IOException(e.Message, e);
}
finally
{
if (sr != null) sr.Close();
}
int timeoutInterval = INCREMENT;
bool init = false;
Regex rgx = null;
Match m = null;
int indexPos = 0;
do
{
try
{
if (!init)
{
rgx = new Regex(@"\b\w+\b", RegexOptions.None,
TimeSpan.FromMilliseconds(timeoutInterval));
m = rgx.Match(input, indexPos);
init = true;
}
else
{
m = m.NextMatch();
}
if (m.Success)
{
if (!exclusions.Contains(m.Value.ToLower()))
wordLengths[m.Value.Length]++;
indexPos += m.Length + 1;
}
}
catch (RegexMatchTimeoutException e)
{
if (e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT)
{
timeoutInterval += INCREMENT;
init = false;
}
else
{
// Rethrow the exception.
throw;
}
}
} while (m.Success);
// If regex completed successfully, calculate number of words and average length.
int nWords = 0;
long totalLength = 0;
for (int ctr = wordLengths.GetLowerBound(0); ctr <= wordLengths.GetUpperBound(0); ctr++)
{
nWords += wordLengths[ctr];
totalLength += ctr * wordLengths[ctr];
}
return new Tuple<int, double>(nWords, totalLength / nWords);
}
}
Imports System.Collections.Generic
Imports System.IO
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim util As New RegexUtilities()
Dim title As String = "Doyle - The Hound of the Baskervilles.txt"
Try
Dim info = util.GetWordData(title)
Console.WriteLine("Words: {0:N0}", info.Item1)
Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2)
Catch e As IOException
Console.WriteLine("IOException reading file '{0}'", title)
Console.WriteLine(e.Message)
Catch e As RegexMatchTimeoutException
Console.WriteLine("The operation timed out after {0:N0} milliseconds",
e.MatchTimeout.TotalMilliseconds)
End Try
End Sub
End Module
Public Class RegexUtilities
Public Function GetWordData(filename As String) As Tuple(Of Integer, Double)
Const MAX_TIMEOUT As Integer = 1000 ' Maximum timeout interval in milliseconds.
Const INCREMENT As Integer = 350 ' Milliseconds increment of timeout.
Dim exclusions As New List(Of String)({"a", "an", "the"})
Dim wordLengths(30) As Integer ' Allocate an array of more than ample size.
Dim input As String = Nothing
Dim sr As StreamReader = Nothing
Try
sr = New StreamReader(filename)
input = sr.ReadToEnd()
Catch e As FileNotFoundException
Dim msg As String = String.Format("Unable to find the file '{0}'", filename)
Throw New IOException(msg, e)
Catch e As IOException
Throw New IOException(e.Message, e)
Finally
If sr IsNot Nothing Then sr.Close()
End Try
Dim timeoutInterval As Integer = INCREMENT
Dim init As Boolean = False
Dim rgx As Regex = Nothing
Dim m As Match = Nothing
Dim indexPos As Integer = 0
Do
Try
If Not init Then
rgx = New Regex("\b\w+\b", RegexOptions.None,
TimeSpan.FromMilliseconds(timeoutInterval))
m = rgx.Match(input, indexPos)
init = True
Else
m = m.NextMatch()
End If
If m.Success Then
If Not exclusions.Contains(m.Value.ToLower()) Then
wordLengths(m.Value.Length) += 1
End If
indexPos += m.Length + 1
End If
Catch e As RegexMatchTimeoutException
If e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT Then
timeoutInterval += INCREMENT
init = False
Else
' Rethrow the exception.
Throw
End If
End Try
Loop While m.Success
' If regex completed successfully, calculate number of words and average length.
Dim nWords As Integer
Dim totalLength As Long
For ctr As Integer = wordLengths.GetLowerBound(0) To wordLengths.GetUpperBound(0)
nWords += wordLengths(ctr)
totalLength += ctr * wordLengths(ctr)
Next
Return New Tuple(Of Integer, Double)(nWords, totalLength / nWords)
End Function
End Class
필요한 경우에만 캡처
.NET의 정규식은 정규식 패턴을 하나 이상의 하위 식으로 그룹화할 수 있는 그룹화 구문을 지원합니다. .NET 정규식 언어에서 가장 일반적으로 사용되는 그룹화 구문은 (
번호가 매겨진 캡처링 그룹을 정의하는 하위)
식과 (?<
명명된 캡처링 그룹을 정의하는 이름>
하위)
식입니다. 그룹화 구문은 역참조를 만들고 수량자가 적용되는 하위 식 정의에 필수적입니다.
그러나 이러한 언어 요소를 사용하는 데는 비용이 듭니다. 속성 GroupCollection이(가) 반환한 Match.Groups 개체는 가장 최근의 명명되지 않은 캡처 또는 명명된 캡처로 채워집니다. 단일 그룹화 구문이 입력 문자열에서 여러 부분 문자열을 캡처한 경우, 특정 캡처링 그룹의 CaptureCollection 속성에서 반환된 Group.Captures 객체에 여러 Capture 객체가 채워집니다.
종종 그룹화 구문은 정규식에서만 사용되므로 수량자를 적용할 수 있습니다. 이러한 하위 식에서 캡처한 그룹은 나중에 사용되지 않습니다. 예를 들어 정규식 \b(\w+[;,]?\s?)+[.?!]
은 전체 문장을 캡처하도록 설계되었습니다. 다음 표에서는 이 정규식 패턴의 언어 요소와 개체 Match 및 Match.Groups 컬렉션에 Group.Captures 미치는 영향에 대해 설명합니다.
패턴 | 설명 |
---|---|
\b |
단어 경계에서부터 일치를 시작합니다. |
\w+ |
하나 이상의 단어 문자를 찾습니다. |
[;,]? |
0개 또는 1개의 쉼표 또는 세미콜론과 일치합니다. |
\s? |
0개 또는 1개의 공백 문자를 찾습니다. |
(\w+[;,]?\s?)+ |
하나 이상의 반복되는 단어 문자 뒤에 선택적으로 쉼표나 세미콜론이 올 수 있으며, 그 뒤에 선택적으로 공백 문자가 올 수 있습니다. 이 패턴은 정규식 엔진이 문장 끝에 도달할 때까지 여러 단어 문자(즉, 단어)와 선택적 문장 부호 기호의 조합이 반복되도록 필요한 첫 번째 캡처 그룹을 정의합니다. |
[.?!] |
마침표, 물음표 또는 느낌표와 일치합니다. |
다음 예제와 같이 일치 항목이 발견되면 GroupCollection 개체와 CaptureCollection 개체가 모두 일치 항목의 캡처로 채워집니다. 이 경우 캡처링 그룹이 (\w+[;,]?\s?)
존재하므로 수량자를 적용할 수 있으므로 +
정규식 패턴이 문장의 각 단어와 일치할 수 있습니다. 그렇지 않으면 문장의 마지막 단어와 일치합니다.
using System;
using System.Text.RegularExpressions;
public class Group1Example
{
public static void Main()
{
string input = "This is one sentence. This is another.";
string pattern = @"\b(\w+[;,]?\s?)+[.?!]";
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine($"Match: '{match.Value}' at index {match.Index}.");
int grpCtr = 0;
foreach (Group grp in match.Groups)
{
Console.WriteLine($" Group {grpCtr}: '{grp.Value}' at index {grp.Index}.");
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine($" Capture {capCtr}: '{cap.Value}' at {cap.Index}.");
capCtr++;
}
grpCtr++;
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: 'This is one sentence.' at index 0.
// Group 0: 'This is one sentence.' at index 0.
// Capture 0: 'This is one sentence.' at 0.
// Group 1: 'sentence' at index 12.
// Capture 0: 'This ' at 0.
// Capture 1: 'is ' at 5.
// Capture 2: 'one ' at 8.
// Capture 3: 'sentence' at 12.
//
// Match: 'This is another.' at index 22.
// Group 0: 'This is another.' at index 22.
// Capture 0: 'This is another.' at 22.
// Group 1: 'another' at index 30.
// Capture 0: 'This ' at 22.
// Capture 1: 'is ' at 27.
// Capture 2: 'another' at 30.
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim input As String = "This is one sentence. This is another."
Dim pattern As String = "\b(\w+[;,]?\s?)+[.?!]"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In match.Groups
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index)
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index)
capCtr += 1
Next
grpCtr += 1
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is one sentence.' at index 0.
' Group 0: 'This is one sentence.' at index 0.
' Capture 0: 'This is one sentence.' at 0.
' Group 1: 'sentence' at index 12.
' Capture 0: 'This ' at 0.
' Capture 1: 'is ' at 5.
' Capture 2: 'one ' at 8.
' Capture 3: 'sentence' at 12.
'
' Match: 'This is another.' at index 22.
' Group 0: 'This is another.' at index 22.
' Capture 0: 'This is another.' at 22.
' Group 1: 'another' at index 30.
' Capture 0: 'This ' at 22.
' Capture 1: 'is ' at 27.
' Capture 2: 'another' at 30.
하위 식만 사용하여 수량자를 적용하고 캡처된 텍스트에 관심이 없는 경우 그룹 캡처를 사용하지 않도록 설정해야 합니다. 예를 들어 (?:subexpression)
언어 요소는 적용되는 그룹이 일치하는 부분 문자열을 캡처할 수 없도록 합니다. 다음 예제에서는 이전 예제의 정규식 패턴이 .로 \b(?:\w+[;,]?\s?)+[.?!]
변경됩니다. 출력에서 보다시피, 정규식 엔진이 GroupCollection 및 CaptureCollection 컬렉션을 채우지 못하도록 방지합니다.
using System;
using System.Text.RegularExpressions;
public class Group2Example
{
public static void Main()
{
string input = "This is one sentence. This is another.";
string pattern = @"\b(?:\w+[;,]?\s?)+[.?!]";
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine($"Match: '{match.Value}' at index {match.Index}.");
int grpCtr = 0;
foreach (Group grp in match.Groups)
{
Console.WriteLine($" Group {grpCtr}: '{grp.Value}' at index {grp.Index}.");
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine($" Capture {capCtr}: '{cap.Value}' at {cap.Index}.");
capCtr++;
}
grpCtr++;
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: 'This is one sentence.' at index 0.
// Group 0: 'This is one sentence.' at index 0.
// Capture 0: 'This is one sentence.' at 0.
//
// Match: 'This is another.' at index 22.
// Group 0: 'This is another.' at index 22.
// Capture 0: 'This is another.' at 22.
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim input As String = "This is one sentence. This is another."
Dim pattern As String = "\b(?:\w+[;,]?\s?)+[.?!]"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In match.Groups
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index)
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index)
capCtr += 1
Next
grpCtr += 1
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is one sentence.' at index 0.
' Group 0: 'This is one sentence.' at index 0.
' Capture 0: 'This is one sentence.' at 0.
'
' Match: 'This is another.' at index 22.
' Group 0: 'This is another.' at index 22.
' Capture 0: 'This is another.' at 22.
다음 방법 중 하나로 캡처를 사용하지 않도록 설정할 수 있습니다.
(?:subexpression)
언어 요소를 사용합니다. 이 요소는 해당 요소가 적용되는 그룹에서 일치하는 부분 문자열의 캡처를 방지합니다. 중첩된 그룹에서 부분 문자열 캡처를 사용하지 않도록 설정하지 않습니다.ExplicitCapture 옵션을 사용합니다. 정규식 패턴에서 명명되지 않은 또는 암시적 캡처를 모두 사용하지 않도록 설정합니다. 이 옵션을 사용하는 경우 언어 요소로
(?<name>subexpression)
정의된 명명된 그룹과 일치하는 부분 문자열만 캡처할 수 있습니다. ExplicitCapture 플래그는options
클래스 생성자의 Regex 매개 변수나options
정적 일치 메서드의 Regex 매개 변수에 전달할 수 있습니다.언어 요소에서
n
(?imnsx)
옵션을 사용합니다. 이 옵션은 요소가 나타나는 정규식 패턴의 지점에서 명명되지 않은 캡처 또는 암시적 캡처를 모두 사용하지 않도록 설정합니다. 캡처는 패턴의 끝까지 비활성화되거나(-n)
옵션이 호출되지 않은 캡처 또는 암시적 캡처를 허용할 때까지 비활성화됩니다. 자세한 내용은 기타 구문을 참조하세요.언어 요소에서
n
(?imnsx:subexpression)
옵션을 사용합니다. 이 옵션은subexpression
에서 모든 명명되지 않은 또는 암시적 캡처를 비활성화합니다. 명명되지 않은 또는 암시적 중첩 캡처 그룹의 캡처도 사용하지 않도록 설정됩니다.
스레드 안전성
Regex 클래스 자체는 스레드로부터 안전하고 변경할 수 없습니다(읽기 전용). 즉, Regex
모든 스레드에서 개체를 만들고 스레드 간에 공유할 수 있습니다. 일치하는 메서드는 스레드에서 호출하고 전역 상태를 변경하지 않을 수 있습니다.
그러나 반환 Match
된 결과 개체(MatchCollection
및Regex
)는 단일 스레드에서 사용해야 합니다. 이러한 개체의 대부분은 논리적으로 변경할 수 없지만, 구현은 성능을 향상시키기 위해 일부 결과의 계산을 지연시킬 수 있으므로 호출자는 해당 개체에 대한 액세스를 직렬화해야 합니다.
여러 스레드에서 결과 개체를 공유 Regex
해야 하는 경우 동기화된 메서드를 호출하여 이러한 개체를 스레드로부터 안전한 인스턴스로 변환할 수 있습니다. 열거자를 제외하고 모든 정규식 클래스는 스레드로부터 안전하거나 동기화된 메서드를 통해 스레드로부터 안전한 개체로 변환할 수 있습니다.
열거자만 예외입니다. 컬렉션 열거자에 대한 호출을 직렬화해야 합니다. 규칙은 컬렉션을 둘 이상의 스레드에서 동시에 열거할 수 있는 경우 열거자가 트래버스하는 컬렉션의 루트 개체에 열거자 메서드를 동기화해야 한다는 것입니다.
관련 문서
제목 | 설명 |
---|---|
정규식 동작에 대한 의 세부 정보 | .NET에서 정규식 엔진의 구현을 검사합니다. 이 문서에서는 정규식의 유연성에 중점을 두고 정규식 엔진의 효율적이고 강력한 작동을 보장하는 개발자의 책임에 대해 설명합니다. |
백트래킹 | 역추적의 정의와 역추적이 정규식 성능에 미치는 영향을 설명하고 역추적에 대한 대안을 제공하는 언어 요소를 검사합니다. |
정규식 언어 - 빠른 참조 | .NET에서 정규식 언어의 요소를 설명하고 각 언어 요소에 대한 자세한 설명서에 대한 링크를 제공합니다. |
.NET