\ d效率低于[0-9]

wes*_*ton 1214 c# regex performance

我昨天做了评论,其中有人曾用一个答案[0123456789]正则表达式,而不是[0-9]\d.我说使用范围或数字说明符比使用字符集更有效.

我决定今天测试一下,并且我惊讶地发现(至少在C#正则表达式引擎中)\d似乎效率低于其他两个似乎没有太大差别的.这是我的10000个随机字符串1000个随机字符的测试输出,其中5077实际上包含一个数字:

Regular expression \d           took 00:00:00.2141226 result: 5077/10000
Regular expression [0-9]        took 00:00:00.1357972 result: 5077/10000  63.42 % of first
Regular expression [0123456789] took 00:00:00.1388997 result: 5077/10000  64.87 % of first
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是有两个原因:

  1. 我原以为该范围的实施要比套装更有效.
  2. 我无法理解为什么\d会比这更糟糕[0-9].还有\d简单的简写[0-9]吗?

这是测试代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace SO_RegexPerformance
{
    class Program
    {
        static void Main(string[] args)
        {
            var rand = new Random(1234);
            var strings = new List<string>();
            //10K random strings
            for (var i = 0; i < 10000; i++)
            {
                //Generate random string
                var sb = new StringBuilder();
                for (var c = 0; c < 1000; c++)
                {
                    //Add a-z randomly
                    sb.Append((char)('a' + rand.Next(26)));
                }
                //In roughly 50% of them, put a digit
                if (rand.Next(2) == 0)
                {
                    //Replace one character with a digit, 0-9
                    sb[rand.Next(sb.Length)] = (char)('0' + rand.Next(10));
                }
                strings.Add(sb.ToString());
            }

            var baseTime = testPerfomance(strings, @"\d");
            Console.WriteLine();
            var testTime = testPerfomance(strings, "[0-9]");
            Console.WriteLine("  {0:P2} of first", testTime.TotalMilliseconds / baseTime.TotalMilliseconds);
            testTime = testPerfomance(strings, "[0123456789]");
            Console.WriteLine("  {0:P2} of first", testTime.TotalMilliseconds / baseTime.TotalMilliseconds);
        }

        private static TimeSpan testPerfomance(List<string> strings, string regex)
        {
            var sw = new Stopwatch();

            int successes = 0;

            var rex = new Regex(regex);

            sw.Start();
            foreach (var str in strings)
            {
                if (rex.Match(str).Success)
                {
                    successes++;
                }
            }
            sw.Stop();

            Console.Write("Regex {0,-12} took {1} result: {2}/{3}", regex, sw.Elapsed, successes, strings.Count);

            return sw.Elapsed;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Sin*_*ian 1540

\d检查所有Unicode数字,但[0-9]仅限于这10个字符.例如,波斯数字?????????是与之匹配\d但未匹配的Unicode数字的示例[0-9].

您可以使用以下代码生成所有此类字符的列表:

var sb = new StringBuilder();
for(UInt16 i = 0; i < UInt16.MaxValue; i++)
{
    string str = Convert.ToChar(i).ToString();
    if (Regex.IsMatch(str, @"\d"))
        sb.Append(str);
}
Console.WriteLine(sb.ToString());
Run Code Online (Sandbox Code Playgroud)

哪个产生:

012345678901234567890123456789߀߁߂߃߄߅߆߇߈߉012345678901২345678901234567890123456789୦୧୨୩୪୫୬୭୮୯0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙᥆᥇᥈᥉᥊᥋᥌᥍᥎᥏᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙᭐᭑᭒᭓᭔᭕᭖᭗᭘᭙᮰᮱᮲᮳᮴᮵᮶᮷᮸᮹᱀᱁᱂᱃᱄᱅᱆᱇᱈᱉᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙꘠꘡꘢꘣꘤꘥꘦꘧꘨꘩꣐꣑꣒꣓꣔꣕꣖꣗꣘꣙꤀꤁꤂꤃꤄꤅꤆꤇꤈꤉꩐꩑꩒꩓꩔꩕꩖꩗꩘꩙0123456789

  • 这是一个更完整的数字列表,不是0-9:http://www.fileformat.info/info/unicode/category/Nd/list.htm (116认同)
  • 它永远不会被改变 - 直到他们需要改变它. (14认同)
  • @RobertMcKee:Nitpick:完整的unicode字符集实际上是21位(每个16位的17个平面).但是当然21位数据类型是不切实际的,所以如果你使用2的幂数据类型,那么你需要32位. (9认同)
  • @weston Unicode有17个平面,每个平面16位.最重要的角色在基本平面上,但是一些特殊角色(主要是中国人)在补充平面中.处理C#中的那些有点烦人. (8认同)
  • 根据[本维基百科文章](http://en.wikipedia.org/wiki/Plane_(Unicode)),Unicode联盟声明1,114,112代码点(0到0x010FFFF)的限制永远不会改变.它链接到unicode.org,但我没有在那里找到声明(我可能只是错过了它). (3认同)
  • 很酷,虽然应该是`UInt16`?罗伯特的这个链接也显示了我感到惊讶的"\ uFFFF"以上的字符,我认为它只是16位.所以你的代码不会找到这些,例如\ u104A0. (2认同)
  • 该答案已添加到[堆栈溢出正则表达式常见问题解答](http://stackoverflow.com/a/22944075/2736496)中的“字符类”下。 (2认同)

wes*_*ton 266

感谢ByteBlast在文档中注意到这一点.只需更改正则表达式构造函数:

var rex = new Regex(regex, RegexOptions.ECMAScript);
Run Code Online (Sandbox Code Playgroud)

提供新的时间:

Regex \d           took 00:00:00.1355787 result: 5077/10000
Regex [0-9]        took 00:00:00.1360403 result: 5077/10000  100.34 % of first
Regex [0123456789] took 00:00:00.1362112 result: 5077/10000  100.47 % of first
Run Code Online (Sandbox Code Playgroud)

  • 实际上,我认为它取消了对Unicode的支持. (84认同)
  • @ 0xFE:不完全.Unicode转义在`ECMAScript`(`\ u1234`)中仍然有效.它只是"改变意义"的简写字符类(如`\ d`)和消失的Unicode属性/脚本缩写(如`\ p {N}`). (28认同)
  • `RegexOptions.ECMAScript`做什么? (11认同)
  • 这不是"为什么"部分的答案.这是一个"修复症状"的答案.仍然有价值的信息 (9认同)
  • 从[Regular Expression Options](http://msdn.microsoft.com/en-us/library/yd1hzczs.aspx):"为表达式启用符合ECMAScript的行为." (6认同)
  • 通常,Regrex 支持 unicode 匹配。但是 ECMAScript 没有。因此,当使用 RegexOptions.ECMAScript 时,它只匹配 ascii,即 0-9。 (4认同)

İsm*_*kan 116

来自正则表达式中的"\ d"是指数字吗?:

[0-9]不等于\d.[0-9]仅匹配0123456789字符,\d匹配[0-9]和其他数字字符,例如东部阿拉伯数字??????????

  • 根据:http://msdn.microsoft.com/en-us/library/20bw873z.aspx`如果指定了符合ECMAScript的行为,\ d等效于[0-9].` (48认同)
  • @ByteBlast谢谢,使用构造函数:`var rex = new Regex(regex,RegexOptions.ECMAScript);`使它们在性能方面几乎无法区分. (3认同)
  • 请不要"仅仅复制"其他问题的答案.如果问题是重复的,请将其标记为此类. (3认同)
  • 嗯,我错了,或者链接中的这个句子正好相反."\ d匹配任何十进制数字.它等同于\ p {Nd}正则表达式模式,包括标准十进制数字0-9以及许多其他字符集的十进制数字." (2认同)
  • 哦,无论如何,谢谢大家.这个问题对我来说是一个很好的学习. (2认同)

Seb*_*ian 19

一个除了顶端回答来自新浪Iravianian,这里是一个.NET 4.5版本(因为只有该版本支持UTF-16输出,CF前三行)他的代码,充分利用各种的Unicode码点.由于缺乏对更高Unicode平面的适当支持,许多人并不知道总是检查并包括上层Unicode平面.然而,他们有时确实包含一些重要人物.

更新

由于\d不支持正则表达式中的非BMP字符(感谢xanatos),这里使用Unicode字符数据库的版本

public static void Main()
{
    var unicodeEncoding = new UnicodeEncoding(!BitConverter.IsLittleEndian, false);
    Console.InputEncoding = unicodeEncoding;
    Console.OutputEncoding = unicodeEncoding;

    var sb = new StringBuilder();
    for (var codePoint = 0; codePoint <= 0x10ffff; codePoint++)
    {
        var isSurrogateCodePoint = codePoint <= UInt16.MaxValue 
               && (  char.IsLowSurrogate((char) codePoint) 
                  || char.IsHighSurrogate((char) codePoint)
                  );

        if (isSurrogateCodePoint)
            continue;

        var codePointString = char.ConvertFromUtf32(codePoint);

        foreach (var category in new []{
        UnicodeCategory.DecimalDigitNumber,
            UnicodeCategory.LetterNumber,
            UnicodeCategory.OtherNumber})
        {
        sb.AppendLine($"{category}");
            foreach (var ch in charInfo[category])
        {
                sb.Append(ch);
            }
            sb.AppendLine();
        }
    }
    Console.WriteLine(sb.ToString());

    Console.ReadKey();
}
Run Code Online (Sandbox Code Playgroud)

产生以下输出:

DecimalDigitNumber012345678901234567890123456789߀߁߂߃߄߅߆߇߈߉012345678901২345678901234567890123456789୦୧୨୩୪୫୬୭୮୯0123456789012345678901234567890123456789෦෧෨෩෪෫෬෭෮෯012345678901234567890123456789012345678901234567890123456789᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙᥆᥇᥈᥉᥊᥋᥌᥍᥎᥏᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙᪀᪁᪂᪃᪄᪅᪆᪇᪈᪉᪐᪑᪒᪓᪔᪕᪖᪗᪘᪙᭐᭑᭒᭓᭔᭕᭖᭗᭘᭙᮰᮱᮲᮳᮴᮵᮶᮷᮸᮹᱀᱁᱂᱃᱄᱅᱆᱇᱈᱉᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙꘠꘡꘢꘣꘤꘥꘦꘧꘨꘩꣐꣑꣒꣓꣔꣕꣖꣗꣘꣙꤀꤁꤂꤃꤄꤅꤆꤇꤈꤉꧐꧑꧒꧓꧔꧕꧖꧗꧘꧙꧰꧱꧲꧳꧴꧵꧶꧷꧸꧹꩐꩑꩒ ꩔꩕꩖꩗꩘꩙꯰꯱꯲꯳꯴꯵꯶꯷꯸꯹0123456789

LetterNumber

ᛮᛯᛰⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿↀↁↂↅↆↇↈ〇〡〢〣〤〥〦〧〨〩〸〹〺ꛦꛧꛨꛩꛪꛫꛬꛭꛮꛯ

OtherNumber²³¹¼½¾৴৵৶.৸৹୲୳୴୵୶୷௰௱௲౸౹౺౻౼౽౾൰൱൲൳൴൵༪༫༬༭༮༯༰༱༲༳፩፪፫፬፭፮፯፰፱፲፳፴፵፶፷፸፹፺፻፼៰៱៲៳៴៵៶៷៸៹᧚⁰⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟↉①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛⓪⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴⓵⓶⓷⓸⓹⓺⓻⓼⓽⓾⓿❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓⳽㆒㆓㆔㆕㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩㉈㉉㉊㉋㉌㉍㉎㉏㉑㉒㉓㉔㉕㉖㉗㉘㉙㉚㉛㉜㉝㉞㉟㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉㊱㊲㊳㊴㊵㊶㊷㊸㊹㊺㊻㊼㊽㊾㊿꠰꠱꠲꠳꠴꠵

  • 如果我记得很清楚,遗憾的是在.NET`Regex`中不支持非BMP字符.所以最后用正则表达式检查字符> 0xffff是没用的. (4认同)