我现在正在做一些我现在正在处理的代码:
public CommandType GetCommandTypeFromCommandString(String command)
{
if(command.StartsWith(CommandConstants.Acknowledge))
return CommandType.Acknowledge;
else if (command.StartsWith(CommandConstants.Status))
return CommandType.Status;
else if (command.StartsWith(CommandConstants.Echo))
return CommandType.Echo;
else if (command.StartsWith(CommandConstants.Warning))
return CommandType.Warning;
// and so on
return CommandType.None;
}
我想知道在C#中是否有更有效的方法。这段代码需要每秒执行很多次,而且我对完成所有这些字符串比较所花费的时间不太满意。有什么建议? :)
答案 0 :(得分:11)
一种优化方法是使用StringComparison枚举来指定您只需要进行序数比较。像这样:
if(command.StartsWith(CommandConstants.Acknowledge, StringComparison.Ordinal))
return CommandType.Acknowledge;
如果你没有指定一个字符串比较方法,那么当前的文化将用于比较,这会使事情变慢一些。
我做了一些(真的很天真)基准测试:
var a = "foo bar foo";
var b = "foo";
int numTimes = 1000000;
Benchmark.Time(() => a.StartsWith(b, StringComparison.Ordinal), "ordinal", numTimes);
Benchmark.Time(() => a.StartsWith(b), "culture sensitive", numTimes);
产生了以下结果:
ordinal ran 1000000 times in 35.6033 ms culture sensitive ran 1000000 times in 175.5859 ms
您还应该进行比较,以便首先比较最可能的代币(快乐路径)。
Theese优化是一种简单的方法,可以让您当前的实现更好,但如果性能真的很关键(我的意思是非常关键),那么您应该考虑实现某种状态机。
答案 1 :(得分:7)
答案 2 :(得分:3)
这是相当多的工作,但您可以根据每个令牌构建FSM。 FSM将逐个接受命令字符串中的字符;每个令牌都有一个最终状态,并且当命令不以任何令牌开始时,还有一个额外的最终状态。
答案 3 :(得分:3)
我认为你可以用正则表达式和词典做得更好:
static Regex reCommands = new Regex("^(cmd1|cmd2|cmd3|cmd4)", RegexOptions.Compiled);
static Dictionary<string, CommandType> Commands = new Dictionary<string, CommandType>();
private static InitDictionary()
{
Commands.Add("cmd1", cmdType1);
Commands.Add("cmd2", cmdType2);
Commands.Add("cmd3", cmdType3);
Commands.Add("cmd4", cmdType4);
}
public CommandType GetCommandTypeFromCommandString(String command)
{
Match m = reCommands.Match(command);
if (m.Success)
{
return Commands[m.Groups[1].Value];
}
return CommandType.None; // no command
}
答案 4 :(得分:2)
我认为你应该考虑更多的可读性而不是担心效率。这些操作非常快。我是第二个塞尔,这部分代码不太可能成为瓶颈。我会做这样的事情:
public CommandType GetCommandTypeFromCommandString(String command)
{
for(String currentCommand : allCommands) {
if(command.StartsWith(currentCommand))
return currentCommand;
}
return CommandType.None;
}
编辑: 作为事后的想法,如果您知道哪些命令最常使用,您可以订购数组,以便这些命令在开始...如果您保留它们,也可以使用if语句执行此操作。
答案 5 :(得分:2)
编辑:鉴于对StopWatch的一个警告的误解,我的原始答案 与StringComparison.Ordinal一起表现不如StartsWith。即使使用所有正确的选项编译正则表达式,它也会慢一点,其性能可与使用StartsWith而不使用任何StringComparison设置相媲美。但是,正则表达式路径确实可以让您更灵活地匹配模式,而StartsWith却没有,所以我将原来的答案留给后人......
原始答案:
我不得不承认,我不确定你到底在寻找什么 - 但是,在我看来,这种代码通常会解析某些描述的日志文件,以寻找有用的信息。为了节省您长时间进行所有字符串比较,您可以在枚举中生成值的正则表达式,然后使用匹配的项解析正确的枚举项:
public enum CommandType
{
Acknowledge,
Status,
Echo,
Warning
}
static public CommandType? GetCommandTypeFromString(String command)
{
var CommandTypes = String.Join("|", Enum.GetNames(typeof(CommandType)));
var PassedConstant = Regex.Match(command, String.Format("(?i:^({0}))", CommandTypes)).Value;
if (PassedConstant != String.Empty)
return (CommandType)Enum.Parse(typeof(CommandType), PassedConstant, true);
return null;
}
static void Main(string[] args)
{
Console.WriteLine(GetCommandTypeFromString("Acknowledge that I am great!").ToString());
}
这将从我的字符串的开头拉出CommandType.Acknowledge,但只有当它存在于字符串的开头时...它还会拉出其他正确的CommandTypes。
对接受的答案进行类似的基准测试,我的性能提升了40%。我在10421毫秒内运行了接受的代码超过一百万次迭代,但我的运行时间只有6459毫秒。
当然,虽然您使用的if语句可能看起来不如您所希望的那样高效,但它仍然比使用正则表达式更容易阅读......
答案 6 :(得分:1)
创建
IDictionary<string,CommandType>
并用值填充它。
您无需比较所有内容......只需在表格中查找即可。
您还需要更好地定义命令语法。在命令和行的其余部分之间需要一个空格,例如......
答案 7 :(得分:1)
多少次“很多次”?我严重怀疑这部分代码是瓶颈。你可以用一个基于每个命令的第一个字母的switch语句来优化它,假设它们都是不同的。
但话又说回来,这真的很有用吗?我不会打赌它。
答案 8 :(得分:1)
我做了类似扩展方法的事情:
public static bool StartsWith(this string s, params string[] candidate)
{
string match = candidate.FirstOrDefault(t => s.StartsWith(t));
return match != default(string);
}
现在这只是一个谓词,它返回数组中的字符串是否以给定字符串开头,但您可以稍微修改它:
public static int PrefixIndex(this string s, params string[] candidate)
{
int index = -1;
string match = candidate.FirstOrDefault(t => { index++; return s.StartsWith(t); });
return match == default(string) ? -1 : index;
}
在使用中它将是:
int index = command.PrefixIndex(tokenStrings);
if (index >= 0) {
// convert to your enum
}
在评论中,我看到你想在1/40秒内进行30次字符串比较。我的朋友,你应该可以在1 MHz的机器上做到这一点。在1/40秒内完成数以千计的字符串比较应该没有汗水。
答案 9 :(得分:1)
特里肯定会成为最快的方法。如果前缀长度相同,您可以通过散列前缀来获得更快的实现,以获取数组的索引,但如果您不知道要散列多少字节,则会出现故障。
答案 10 :(得分:-3)
注意:我没有演示如何使用异常处理来控制程序流。如果Enum的字符串名称不存在,Enum.Parse将抛出异常。 Catch子句只返回默认的CommandType为None,就像提问者的示例代码一样。
如果对象只是为了返回实际的Enum对象而给定字符串名称,则不能使用:
try
{
return (CommandType)Enum.Parse(typeof(CommandType), strCmdName, true);
}
catch (Exception)
{
return CommandType.None;
}