在 C# 中搜索字节数组中的最长模式

Posted

技术标签:

【中文标题】在 C# 中搜索字节数组中的最长模式【英文标题】:Search longest pattern in byte array in C# 【发布时间】:2012-04-10 23:28:06 【问题描述】:

我需要编写有效且快速的方法来搜索给定模式的字节数组。 我是这样写的,你怎么看,如何改进?而且它有一个错误,它不能返回长度为1的匹配。

public static bool SearchByteByByte(byte[] bytes, byte[] pattern)
    
        bool found = false;
        int matchedBytes = 0;
        for (int i = 0; i < bytes.Length; i++)
        
            if (pattern[0] == bytes[i] && bytes.Length - i >= pattern.Length)
            
                for (int j = 1; j < pattern.Length; j++)
                
                    if (bytes[i + j] == pattern[j])
                    
                        matchedBytes++;
                        if (matchedBytes == pattern.Length - 1)
                        
                            return true;
                        
                        continue;
                    
                    else
                    
                        matchedBytes = 0;
                        break;
                    
                
            
        
        return found;
    

有什么建议吗?

【问题讨论】:

你可以看看字符串搜索算法en.wikipedia.org/wiki/String_searching_algorithm 最好将算法背后的想法包含在内,而不是在没有解释的情况下粘贴代码。 将您的两个字节数组转换为两个逗号分隔的十六进制字符串并进行正则表达式匹配是否足够有效? 【参考方案1】:

在 grep 中使用的 Boyer-Moore 算法非常有效,并且对于更长的模式大小更有效。我很确定你可以毫不费力地让它适用于字节数组,并且它的wikipedia page 有一个 Java 实现,应该很容易移植到 C#。

更新:

这是 C# 中字节数组的 Boyer-Moore 算法的简化版本的实现。它只使用完整算法的second jump table。根据您所说的数组大小(haystack:2000000 字节,needle:10 字节),它比简单的逐字节算法快大约 5-8 倍。

    static int SimpleBoyerMooreSearch(byte[] haystack, byte[] needle)
    
        int[] lookup = new int[256];
        for (int i = 0; i < lookup.Length; i++)  lookup[i] = needle.Length; 

        for (int i = 0; i < needle.Length; i++)
        
            lookup[needle[i]] = needle.Length - i - 1;
        

        int index = needle.Length - 1;
        var lastByte = needle.Last();
        while (index < haystack.Length)
        
            var checkByte = haystack[index];
            if (haystack[index] == lastByte)
            
                bool found = true;
                for (int j = needle.Length - 2; j >= 0; j--)
                
                    if (haystack[index - needle.Length + j + 1] != needle[j])
                    
                        found = false;
                        break;
                    
                

                if (found)
                    return index - needle.Length + 1;
                else
                    index++;
            
            else
            
                index += lookup[checkByte];
            
        
        return -1;
    

【讨论】:

我读过,Boyer-Moore 算法在大字母表上的速度并不快。字节字母是 256。它会很快吗? 说实话,我不确定。您可以尝试对其进行基准测试,但恐怕如果不自己进行基准测试,我就无法告诉您。出于兴趣,您的模式有多长? 大约 10 个字节,但我需要搜索的数组大约有 2 000 000 项长。 可以用更多的输入参数修改这段代码吗?大海捞针索引从哪里开始搜索?我知道我可以复制数组并删除第一项,但在大数组中它很慢,所以从给定索引开始搜索会更好。 是的,非常容易。只需在方法中添加一个int index 参数,使其签名如下所示:static int SimpleBoyerMooreSearch(byte[] haystack, byte[] needle, int index),并删除方法主体中的int index 赋值。然后将index 增加needle.Length - 1,这是必要的,因为算法的工作方式(即从针的末端开始并返回)。【参考方案2】:

它有一个错误,它不能返回长度为1的匹配

要解决这个问题,从零开始内循环:

public static bool SearchByteByByte(byte[] bytes, byte[] pattern)

    bool found = false;
    int matchedBytes = 0;
    for (int i = 0; i < bytes.Length; i++)
    

        if (pattern[0] == bytes[i] && bytes.Length - i >= pattern.Length)
        

            for (int j = 0; j < pattern.Length; j++) // start from 0
            
                if (bytes[i + j] == pattern[j]) 
                
                    matchedBytes++;
                    if (matchedBytes == pattern.Length) // remove - 1                    
                        return true;

                    continue;
                
                else
                
                    matchedBytes = 0;
                    break;
                
            
        
    
    return found;

更新:这是你在奉承和删除局部变量后的搜索算法(不需要它们)

public static bool SearchByteByByte(byte[] bytes, byte[] pattern)

    for (int i = 0; i < bytes.Length; i++)
    
        if (bytes.Length - i < pattern.Length)
            return false;

        if (pattern[0] != bytes[i])
            continue;

        for (int j = 0; j < pattern.Length; j++)
        
            if (bytes[i + j] != pattern[j])
                break;                    

            if (j == pattern.Length - 1)
                return true;
        
    

    return false;

【讨论】:

顺便说一句,您需要在获取数组的第一项之前检查模式数组长度。 没问题。如果您对上次优化有任何疑问,请询问他们:)【参考方案3】:

因此,您实际上正在寻找最长的公共子字符串,因此请参阅 Wikipedia 文章:http://en.wikipedia.org/wiki/Longest_common_substring_problem

... 甚至是参考实现:http://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Longest_common_substring#C.23 -- 当然,您必须将byte[] 替换为string,等等。

【讨论】:

我尝试了最长公共子串算法,但速度很慢。我认为字节算法会更快。在我的算法中,我必须使用字节。 经典定义中的字符串(避开 Unicode,只有 8 位“字符”)无论如何都只是字节数组。

以上是关于在 C# 中搜索字节数组中的最长模式的主要内容,如果未能解决你的问题,请参考以下文章

从 C# 中的字节数组中删除尾随空值

字节数组中的字符串不会在 C# 中被修剪?

使用 C# 从字节数组中解码 dtmf

十六进制字符串到字节数组 C# [重复]

无法将固定大小的字节数组从结构复制到 C# 结构中的另一个数组

在 C# 中组合两个或多个字节数组的最佳方法