首页 新闻 会员 周边 捐助

求 .NET Core 中判断字符串是否由字母、数字、连字符、下划线组成的最快方法

0
悬赏园豆:200 [待解决问题]

判断规则:字符串中只能包含字母、数字、连字符、下划线,字符串最少1个字符,最多150字符。

使用正则表达式判断的代码以及执行速度如下,求比正则表达更快的方法。

class Program
{
    private const int loops = 10000;
    private static readonly Regex _regex = new Regex(@"^[-_a-zA-Z0-9]{1,150}$", RegexOptions.Compiled);

    static void Main(string[] args)
    {
        Stopwatch sw = Stopwatch.StartNew();

        for (var i = 0; i < loops; i++)
        {
            var str = Guid.NewGuid().ToString();
            _regex.IsMatch(str);
        }

        sw.Stop();
        Console.WriteLine($"Elapsed Ticks: {(int)(sw.ElapsedTicks / loops)}");
    }
}

使用正则表达式,执行1万次的平均耗时在 45 ticks 左右。

dudu的主页 dudu | 高人七级 | 园豆:30775
提问于:2019-08-23 21:07
< >
分享
所有回答(8)
0

var validateData //you value;

if(validateData.Length<??)throw InvalidateException("Min Lenth must be 1");

validateData.Foreach(t=>t.any(范围外))//参照ascII

正则更适合注册等低频调用,除了copy方便,实际学习成本高昂~~

花飘水流兮 | 园豆:13615 (专家六级) | 2019-08-23 21:24

这cpu开销好大啊

支持(0) 反对(0) 合纵师 | 园豆:12 (初学一级) | 2019-08-23 21:35

@合纵师: 难道正则不是 for 出来的?计算机根本就是for,机器的本质就是for,人做创造才是一次性的~~~机器只会机械运动~,实在还不爽 只能换指针了~~~仅仅快得到一丢丢的检查时间

支持(0) 反对(0) 花飘水流兮 | 园豆:13615 (专家六级) | 2019-08-23 21:47

@花飘水流兮: 你这个应该比上面的开销大

支持(0) 反对(0) 合纵师 | 园豆:12 (初学一级) | 2019-08-30 10:08

@合纵师: 建议看看string类实现源码,以及正则实现源码。前者你可以直接参考<<c++ primary>>,应该是version 小于4都有string实现章节,至于正则作为一个通用的,首先需要解析一些string的规则(这次开销就不小了),能快就奇怪了。

支持(0) 反对(0) 花飘水流兮 | 园豆:13615 (专家六级) | 2019-08-30 10:47

@花飘水流兮: 正则还是很快的,看你怎么想,你这个2个for实现,一个foreach一个any,cpu消耗指数增长

支持(0) 反对(0) 合纵师 | 园豆:12 (初学一级) | 2019-10-12 21:14

@合纵师: 见倒数第二个回答,可以看了再回头看你的这个评论。优秀的算法可以减少for(重复循环,这也是机器的本质)的使用,但逃不出这个现实。

支持(0) 反对(0) 花飘水流兮 | 园豆:13615 (专家六级) | 2019-10-12 22:28
0

这和cpu有关吧,我运行上述代码在10-13之间,然后从上述代码看将创建正则表达式放在外围且设置可编译选项是性能最快的应该,想不到还有什么优化的点了。dudu老大试试如下呢,看看会不会改善一点,搞一个静态类存放创建的正则表达字段

Jeffcky | 园豆:2789 (老鸟四级) | 2019-08-24 00:08

我测试时用的是阿里云4核8G的服务器

支持(0) 反对(0) dudu | 园豆:30775 (高人七级) | 2019-08-24 14:21
0
  1. 应该将var str = Guid.NewGuid().ToString();放在记时统计外面
  2. 逐个字符判断,类似如下
static bool IsMatch(string s)
        {
            if (string.IsNullOrEmpty(s)) return false;
            if (s.Length > 150) return false;
            for (int i = 0; i < s.Length; i++)
            {
                char c = s[i];
                bool isValid = c >= 65 && c <= 90 ||
                    c >= 97 && c <= 122 ||
                    c >= 48 && c <= 57 ||
                    c == 45 ||
                    c == 95;
                if (!isValid) return false;
            }
            return true;
        }
jello chen | 园豆:7336 (大侠五级) | 2019-08-24 09:51

Guid.NewGuid().ToString() 排除后,耗时在 35-40 tickets 之间

支持(0) 反对(0) dudu | 园豆:30775 (高人七级) | 2019-08-24 14:21

@dudu: 我用上面的方法,10,000次平均2ticks,1,000,000次平均1ticks。我的电脑配置如下:
操作系统:windows 10教育版 64位
处理器:i7-6700HQ
内存:8GB

支持(0) 反对(0) jello chen | 园豆:7336 (大侠五级) | 2019-08-24 16:08

@dudu: 我用.net core 2.2和3.0分别测试了我上面的方法和下面你贴出来的方法,基本一致,平均都在2ticks之内

支持(0) 反对(0) jello chen | 园豆:7336 (大侠五级) | 2019-08-24 16:29
0

想到一个 类似 bitMap 思想的方法,先把初始字母,数字,下划线,横杆。算了一下,这些的ASCII 码最大122,那么 构造一个 123 长度 的 int[] 数组 private static int[] _intBitMap = new int[123];, 先开始给这个数组初始化赋值:

 private static void InitializeBitMapData()
        {
            for (char c = 'a'; c <= 'z'; c++)
            {
                _intBitMap[c] = 1;
            }

            for (char c = 'A'; c <= 'Z'; ++c)
            {
                _intBitMap[c] = 1;
            }

            for (int i = 0; i <= 9; ++i)
            {
                _intBitMap[i] = 1;
            }

            _intBitMap[95] = 1; // _ 95
            _intBitMap[45] = 1; // - 45
        }

然后在判断时,只需要查看命中的个数,

 private static bool CalculateBitMap(string value)
        {
            var charValue = value.ToCharArray();
            var count = 0;
            for (int i = 0; i < value.Length; ++i)
            {
                count += _intBitMap[charValue[i]];
            }

            return count >= 1 && count <= 150;
        }

测试了一下,10000 的循环 大概 2-2.5 倍的差距:
截图:

BUTTERAPPLE | 园豆:3190 (老鸟四级) | 2019-08-24 13:28

首先0-9的ascii码不是0-9,其次你的数组会越界。

数组的长度应该是65535。

支持(0) 反对(0) Shendu.CC | 园豆:2138 (老鸟四级) | 2019-08-25 09:58

@Shendu.CC: 嗯嗯,ASCII 码找错了,,,,数组 还是123 就够了,判断进来的 ASCII 超出,就直接跳过了,判断命中个数就好。

支持(0) 反对(0) BUTTERAPPLE | 园豆:3190 (老鸟四级) | 2019-08-26 09:06

@BUTTERAPPLE: 是的,没错。

支持(0) 反对(0) Shendu.CC | 园豆:2138 (老鸟四级) | 2019-08-26 11:39
0

使用 ReadOnlySpan ,目前找到的最快方法是 3 ticks,

class Program
{
    private const int loops = 10000;

    static void Main(string[] args)
    {
        Stopwatch sw = Stopwatch.StartNew();
        var str = Guid.NewGuid().ToString();
        for (var i = 0; i < loops; i++)
        {
            var result = str.AsSpan().ValidateWord();
        }
        sw.Stop();

        var ticks = (int)(sw.ElapsedTicks / loops);
        Console.WriteLine($"Elapsed ticks: {ticks}");
        //Output is "Elapsed ticks: 3"
    }
}

public static class CharConstraints
{
    public static bool ValidateWord(this ReadOnlySpan<char> span)
    {
        if(span.Length > 150)
        {
            return false;
        }

        foreach (var c in span)
        {
            //IsLetterDigitHyphenUnderscore
            if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'))
            {
                return false;
            }
        }

        return true;
    }
}

.NET Core 版本是 3.0.100-preview8-013656 ,电脑配置是4核8G的阿里云服务器,操作系统是 Windows Server 2106

dudu | 园豆:30775 (高人七级) | 2019-08-24 15:19

这里并不用转成span吧,只是一个读操作,并不会创造新的string。

支持(0) 反对(0) Timetombs | 园豆:3959 (老鸟四级) | 2019-08-24 19:59

@blackheart: 相差很大,你可以自己对比一下

支持(0) 反对(0) dudu | 园豆:30775 (高人七级) | 2019-08-24 21:12

@dudu:

我试了一下,没有差别,都是3tick. .net core 2.2

using System;
using System.Diagnostics;

class Program
{
    private const int loops = 10000;

    static void Main(string[] args)
    {
        Stopwatch sw1 = Stopwatch.StartNew();
        var str = Guid.NewGuid().ToString();
        for (var i = 0; i < loops; i++)
        {
            var result = str.AsSpan().ValidateWordSpan();
        }
        sw1.Stop();

        var ticks = (int)(sw1.ElapsedTicks / loops);
        Console.WriteLine($"Elapsed ticks 1: {ticks}");

        Stopwatch sw2 = Stopwatch.StartNew();
        for (var i = 0; i < loops; i++)
        {
            var result = str.ValidateWordString();
        }
        sw2.Stop();

        var ticks2 = (int)(sw2.ElapsedTicks / loops);
        Console.WriteLine($"Elapsed ticks 2: {ticks2}");
    }
}

public static class CharConstraints
{
    public static bool ValidateWordString(this String str)
    {
        if (str.Length > 150)
        {
            return false;
        }

        foreach (var c in str)
        {
            //IsLetterDigitHyphenUnderscore
            if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'))
            {
                return false;
            }
        }

        return true;
    }

    public static bool ValidateWordSpan(this ReadOnlySpan<char> span)
    {
        if (span.Length > 150)
        {
            return false;
        }

        foreach (var c in span)
        {
            //IsLetterDigitHyphenUnderscore
            if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'))
            {
                return false;
            }
        }

        return true;
    }
}
支持(0) 反对(0) Timetombs | 园豆:3959 (老鸟四级) | 2019-08-24 21:54

@blackheart: 不会意思,的确一样,当时我比较时用的是 str.Any LINQ ,慢是因为 LINQ

支持(0) 反对(0) dudu | 园豆:30775 (高人七级) | 2019-08-25 11:21
0

对于这种单字符的范围简单的规则,应该是硬编码算法更快。并且比正则表达式要快的多吧,毕竟正则表达式是通用算法,效率比不过有较好实现的专有算法的。算法中可以考虑通过span或者指针提高遍历字符串取字符的效率。

另外,楼主的计时算法有问题,把生成guid的时间算进去了,生成guid的时间应该比判断算法的时间大不少的。应该是先生成guid再计时。

天方 | 园豆:5432 (大侠五级) | 2019-08-24 16:32
0

使用 BitArray 可以有效的提高速度,只需要 8k 的内存。

在 Debug 版本下跑出来是 2 Ticks

class Program
{
    private const int loops = 10000;
    private static readonly BitArray _acceptedChars = new BitArray(char.MaxValue - char.MinValue);

    static void Main(string[] args)
    {
        for (var c = 'a'; c <= 'z'; c++)
        {
            _acceptedChars[c] = true;
        }

        for (var c = 'A'; c <= 'Z'; c++)
        {
            _acceptedChars[c] = true;
        }

        for (var c = '0'; c <= '9'; c++)
        {
            _acceptedChars[c] = true;
        }

        _acceptedChars['_'] = true;
        _acceptedChars['-'] = true;

        var testCases = new string[loops];
        for (var i = 0; i < loops; i++)
        {
            testCases[i] = Guid.NewGuid().ToString();
        }

        var result = true;
        var sw = Stopwatch.StartNew();
        for (var i = 0; i < loops; i++)
        {
            foreach (var c in testCases[i])
            {
                result = _acceptedChars[c];
            }
        }
        sw.Stop();
        Console.WriteLine($"Elapsed Ticks: {(int)(sw.ElapsedTicks / loops)}");
    }
}
沈星繁 | 园豆:1096 (小虾三级) | 2019-08-25 16:10
0

我这里t440s,.net core 3.0
跑的结果是:Elapsed Ticks: 3
没发现问题呢!

张朋举 | 园豆:1936 (小虾三级) | 2019-09-09 12:29
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册