使用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中重写
你能帮我一下吗?
更新问题后,适用于您的新正则表达式应该是:
^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)