不能直接用 strings.Contains 处理通配符匹配,因为它仅支持子串判断,无法解析 *(匹配任意长度字符串)和 ?(匹配单个字符)的语义;硬套正则引擎又过于重量级。

为什么不能直接用 strings.Contains 处理通配符匹配
因为 strings.Contains 只做子串判断,不理解 * 和 ? 的语义:* 应匹配任意长度(含零)字符,? 匹配单个任意字符。硬套正则(如 regexp.Compile)又太重——每次编译开销大,且通配符模式简单,没必要引入完整正则引擎。
用动态规划实现 O(m×n) 时间的通配符匹配
标准 DP 解法:设 dp[i][j] 表示模式前 i 个字符能否匹配字符串前 j 个字符。关键在于处理 *:它可“跳过”自身去匹配空串(dp[i-1][j]),也可“吃掉”当前字符继续匹配(dp[i][j-1])。? 直接等价于任意单字符,只需检查 dp[i-1][j-1]。
实操建议:
- 初始化
dp[0][0] = true;若模式以连续*开头,对应dp[i][0] = true - 遍历时,
dp[i][j]仅依赖上一行和左一列,可用滚动数组优化至 O(n) 空间 - 避免切片越界:模式索引为
i-1,字符串索引为j-1 - Go 中二维 slice 初始化易出错,推荐用
make([][]bool, len(pattern)+1)后逐行分配
递归回溯 + 记忆化比 DP 更简洁且实际更快?
对多数真实场景(如文件名匹配、配置项过滤),模式和字符串都较短,深度优先+记忆化反而更直观、缓存局部性更好。Go 中用 map[[2]int]bool 缓存 (i, j) 状态即可。
注意点:
- 递归入口要预处理连续
*——"a**b"等价于"a*b",否则栈深度无谓增加 - 必须剪枝:当模式剩余全是
*,直接返回 true;当字符串已耗尽但模式还有非*字符,直接 false - 不要用闭包捕获变量传参,显式传
i,j,pattern,s更利于逃逸分析
生产环境该选哪个?看场景再决定
高频调用且模式固定(如 Web 路由匹配):预编译成状态机(类似 filepath.Match 内部逻辑),避免每次 DP 或递归开销。
低频、模式动态(如 CLI 参数 glob 过滤):直接复用 path.Match ——它已用高效回溯实现,且经过多年验证;自己重写反而容易漏 corner case(如 **、编码边界)。
真正需要自定义行为时(比如忽略大小写 + 通配符),才基于上述 DP 或回溯改写,但务必加测试覆盖:"*a", "a*", "a?b", "" 和空模式、空字符串这些边界。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











