使用RegEx重写IsHexString方法

本文关键字:方法 IsHexString 重写 RegEx 使用 | 更新日期: 2023-09-27 18:23:40

我得到了一个检查字符串是否为有效十六进制字符串的方法:

public bool IsHex(string value)
{
  if (string.IsNullOrEmpty(value) || value.Length % 2 != 0)
    return false;
  return 
    value.Substring(0, 2) == "0x" &&
    value.Substring(2)
      .All(c => (c >= '0' && c <= '9') ||
                (c >= 'a' && c <= 'f') ||
                (c >= 'A' && c <= 'F'));
}

规则是:
表达式必须由偶数十六进制数字(0-9、A-F、A-F)组成
字符0x必须是表达式中的前两个字符

我相信它可以用一种更干净、更高效的方式在regex中重写
你能帮我一下吗?

使用RegEx重写IsHexString方法

更新问题后,适用于您的新正则表达式应该是:

^0x(?:[0-9A-Fa-f]{2})+$

其中,为了提高效率,我使用(?:进行非捕获分组。{2}表示您需要上一个表达式中的两个(即两个十六进制字符),+表示您需要一个或多个十六进制字母。请注意,这不允许0x作为有效值。

效率

《奥德》提到了一些关于效率的东西。我不知道你的要求,所以我认为这比其他任何事情都更能锻炼头脑。正则表达式的跳跃长度与匹配的最小正则表达式一样长。例如,在10000个大小为50-5000个字符的可变输入字符串上尝试我自己的regex,都是正确的,它在1.1秒内运行。

当我尝试以下正则表达式时:

^0x(?:[0-9A-Fa-f]{32})+(?:[0-9A-Fa-f]{2})+$

它在0.67秒内跑得快了40%。但是要小心。知道您的输入就是知道如何编写高效的正则表达式。例如,如果regex失败,它将进行大量的回溯。如果我的输入字符串有一半的长度不正确,那么对于相同的输入,运行时间会爆炸到大约34秒,即3000%(!)。

如果大多数输入字符串都很大,则会变得更加棘手。如果99%的输入是有效长度的,那么所有的都是>4130个字符,只有少数不是,写

^0x(?:[0-9A-Fa-f]{4096})+^0x(?:[0-9A-Fa-f]{32})+(?:[0-9A-Fa-f]{2})+$

效率更高,时间更长。然而,如果许多具有不正确的length % 2 = 0,则由于反向跟踪,这是计数器有效的。

最后,如果大多数字符串都满足偶数规则,并且只有一些或多个字符串包含错误的字符,则速度会加快:包含错误字符的输入越多,性能就越好。也就是说,因为当它发现一个无效字符时,它可以立即爆发。

结论:如果您的输入混合了小、大、错误的字符、错误的计数,您最快的方法是使用检查字符串长度(在.NET中是瞬时的)和使用高效的正则表达式的组合。

因此,基本上,您需要检查数字是否以0x开头,并以0-9和/或a-F(非空)序列继续。可以很容易地指定为正则表达式:

return RegEx.IsMatch(value, "^0x[0-9A-Fa-f]+$")

我不知道你为什么要做value.Length % 2 != 0检查。。。"0x1"不是一个有效的十六进制数字吗?此外,我的函数在"0x"上返回false,而您的函数将返回true。如果要更改,请将正则表达式中的+(=一个或多个)替换为*(=零个或多个子)。

编辑:既然你已经证明了你的"偶数"要求,我建议你使用Abel的RegEx。如果你这样做,我建议你调用你的方法IsMsSqlHex或类似的东西来记录它不遵循"常见"的十六进制规则。

Diatrib:如果你关心速度,那就忘了Regex吧。Regex是一个NFA,因此在大多数情况下,它比DFA或手工编写的解析器慢。

忽略你在这里要求的Regex可能会更高效(即使你的实现可能很好——它确实分配了字符串):

static bool IsHex(string value)
{
    if (string.IsNullOrEmpty(value) || value.Length < 3)
        return false;
    const byte State_Zero = 0;
    const byte State_X = 1;
    const byte State_Value = 2;
    var state = State_Zero;
    for (var i = 0; i < value.Length; i++)
    {
        switch (value[i])
        {
            case '0': 
                {
                    // Can be used in either Value or Zero.
                    switch (state)
                    {
                        case State_Zero: state = State_X; break;
                        case State_X: return false;
                        case State_Value: break;
                    }
                }
                break;
            case 'X': case 'x': 
                {
                    // Only valid in X.
                    switch (state)
                    {
                        case State_Zero: return false;
                        case State_X: state = State_Value; break;
                        case State_Value: return false;
                    }
                }
                break;
            case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
            case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
            case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
                {
                    // Only valid in Value.
                    switch (state)
                    {
                        case State_Zero: return false;
                        case State_X: return false;
                        case State_Value: break;
                    }
                }
                break;
            default: return false;
        }
    }
    return state == State_Value;
}

如果我能正确地了解你想要实现的目标,也许这个功能会更好地满足你的需求:

static bool ParseNumber(string value, out int result)
{
    if (string.IsNullOrEmpty(value))
    {
        result = 0;
        return false;
    }
    if (value.StartsWith("0x"))
        return int.TryParse(value.Substring(2), NumberStyles.AllowHexSpecifier, null, out result);
    else
        return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
}

只是为了好玩,我去介绍了一下:

  • Abel的Regex没有static readonly缓存,正如我在Heinzi的回答中所描述的那样
  • Abel的Regex与static readonly缓存
  • 我的实施

在我的笔记本电脑上的结果(发布/没有调试器):

  • 没有编译/缓存的Regex Regex耗时8137ms(2次缓存,20次手写)
  • Regex与编译/缓存Regex耗时3463ms(8倍手写)
  • 手写耗时397ms(1x)