You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#如何优化遍历400万条数据的List<string>快速过滤用户输入字符串

C#超大型多模式字符串匹配优化方案

原代码性能瓶颈

  • 遍历400万条模式串逐次对message做扫描,每一次Contains和Replace操作都需要完整遍历一次message字符串,时间复杂度为O(k*n)(k为模式串数量,n为message长度),计算开销极高
  • 多次Replace会生成大量临时字符串,额外增加GC压力

核心优化方案:使用Aho-Corasick多模式匹配算法

该算法专门针对「单文本匹配大量模式串」的场景设计,仅需1次遍历文本即可找出所有匹配的模式串,时间复杂度降低到O(n + m + z),其中n是文本长度,m是所有模式串的总字符数,z是匹配结果数量,完全不受400万模式串量级的制约。

实现要点

  • 因为excludes是静态全局变量,仅需在程序启动时一次性构建AC自动机,预处理开销只会产生一次,不会平摊到每次message处理逻辑中
  • 预处理阶段先对excludes做去重,避免重复加载相同模式串浪费资源
  • 匹配到所有需要删除的子串后,一次性遍历message拼接出最终结果,避免多次Replace生成临时字符串

示例实现代码

// AC自动机节点定义
public class AcNode
{
    public Dictionary<char, AcNode> Children { get; } = new();
    public string? Pattern { get; set; }
    public AcNode? Fail { get; set; }
}

// 静态预处理构建自动机,程序生命周期内仅执行一次
private static AcNode BuildAcAutomaton(List<string> excludes)
{
    var root = new AcNode();
    // 先去重,过滤空字符串
    foreach (var pattern in excludes.Distinct().Where(p=>!string.IsNullOrEmpty(p)))
    {
        var current = root;
        foreach (var c in pattern)
        {
            if (!current.Children.TryGetValue(c, out var next))
            {
                next = new AcNode();
                current.Children[c] = next;
            }
            current = next;
        }
        current.Pattern = pattern;
    }
    // BFS构建失败指针
    var queue = new Queue<AcNode>();
    foreach (var child in root.Children.Values)
    {
        child.Fail = root;
        queue.Enqueue(child);
    }
    while (queue.Count > 0)
    {
        var current = queue.Dequeue();
        foreach (var (c, child) in current.Children)
        {
            var failNode = current.Fail;
            while (failNode != null && !failNode.Children.ContainsKey(c))
            {
                failNode = failNode.Fail;
            }
            child.Fail = failNode?.Children.GetValueOrDefault(c) ?? root;
            queue.Enqueue(child);
        }
    }
    return root;
}

// 静态资源初始化
private static readonly List<string> excludes = new List<string>(4000000);
private static readonly AcNode _acRoot = BuildAcAutomaton(excludes);

// 业务逻辑调用的核心处理方法
public string ProcessMessage(string message)
{
    var matches = new List<(int Start, int Length)>();
    var current = _acRoot;
    // 单次遍历message收集所有匹配项
    for (int i = 0; i < message.Length; i++)
    {
        var c = message[i];
        while (current != _acRoot && !current.Children.ContainsKey(c))
        {
            current = current.Fail;
        }
        if (current.Children.TryGetValue(c, out var next))
        {
            current = next;
        }
        // 递归收集失败指针链上的所有匹配
        var temp = current;
        while (temp != _acRoot)
        {
            if (temp.Pattern != null)
            {
                matches.Add((i - temp.Pattern.Length + 1, temp.Pattern.Length));
            }
            temp = temp.Fail;
        }
    }
    // 无匹配直接返回原字符串
    if (matches.Count == 0) return message;
    // 合并重叠匹配区间,构建结果
    matches.Sort((a, b) => a.Start.CompareTo(b.Start));
    var sb = new StringBuilder(message.Length);
    int lastPos = 0;
    foreach (var match in matches)
    {
        if (match.Start >= lastPos)
        {
            sb.Append(message.AsSpan(lastPos, match.Start - lastPos));
            lastPos = match.Start + match.Length;
        }
        else if (match.Start + match.Length > lastPos)
        {
            lastPos = match.Start + match.Length;
        }
    }
    sb.Append(message.AsSpan(lastPos));
    return sb.ToString();
}

可叠加的补充优化点

  • 如果业务允许,可提前过滤长度大于当前message的模式串,无需加入自动机匹配
  • 若业务要求不区分大小写,可在预处理和匹配阶段统一转小写/大写,避免大小写匹配的额外开销
  • 若excludes存在大量前缀相同的字符串,AC自动机的前缀树结构会自动合并冗余,进一步降低内存和匹配开销

内容的提问来源于stack exchange,提问作者Persson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 08:06:03