当前位置: 首页 > news >正文

如何优化字符串替换:四种实现方案对比与性能分析

问题背景

我们在处理商品名称时,常常需要去掉一些不需要的关键词

例如:

原商品名:

HUAWEI Pura 70 Pro 国家补贴500元 羽砂黑 12GB+512GB 超高速风驰闪拍 华为鸿蒙智能手机

希望替换后:

HUAWEI Pura 70 Pro 羽砂黑 12GB+512GB 超高速风驰闪拍 华为鸿蒙智能手机

替换掉的关键词是:“国家补贴500元”。

你可能会说:“这很简单嘛,用 skuName.replace("国家补贴500元", "") 不就搞定了?”
确实,当关键词不多时这样完全可以
但如果我们要处理的是几百上千个关键词,就不那么简单了!

很多项目上线后,用 String.replace 处理大量关键词会瞬间把CPU打满,程序几乎卡死,根本无法用。

其实这和“敏感词过滤”是一个道理。为了解决这个问题,就需要用更高效的算法,比如下面介绍的 Aho-Corasick 自动机算法(简称 AC 自动机)


解决方案

我们设计了一个统一的接口 Replacer,定义了 replaceKeywords(String text) 方法,便于对不同替换方案进行比较。

public interface Replacer {String replaceKeywords(String text);
}

下面我们介绍四种方案。


1 最简单的 String.replace 方案

这是最基础的办法,也就是把每个关键词依次替换掉。为了避免“短关键词”干扰“长关键词”,我们先把关键词按长度从长到短排序

public class StrReplacer implements Replacer {private final List<String> keyWordList;public StrReplacer(String keyWords) {this.keyWordList = Arrays.asList(keyWords.split(";"));keyWordList.sort((a, b) -> b.length() - a.length());}@Overridepublic String replaceKeywords(String text) {String result = text;for (String keyword : keyWordList) {result = result.replace(keyword, "");}return result;}
}

这个方法适用于关键词不多的场景,简单有效。


2 使用正则表达式优化替换

其实 String.replace() 背后也是在用正则表达式。我们也可以自己构造一个正则表达式,一次性替换所有关键词。

public class PatternReplacer implements Replacer {private final Pattern pattern;public PatternReplacer(String keyWords) {List<String> keywords = Arrays.asList(keyWords.split(";"));keywords.sort((a, b) -> b.length() - a.length());String regex = keywords.stream().map(Pattern::quote).collect(Collectors.joining("|"));this.pattern = Pattern.compile(regex);}@Overridepublic String replaceKeywords(String text) {return pattern.matcher(text).replaceAll("");}
}

这个方法适用于关键词数量中等的情况,性能比循环调用 replace() 稍好。


3 使用 Aho-Corasick(AC 自动机)算法

这是一个经典的多关键词匹配算法,可以在一次遍历中找出所有匹配的关键词,效率非常高。

我们使用现成的库:

<dependency><groupId>org.ahocorasick</groupId><artifactId>ahocorasick</artifactId><version>0.6.3</version>
</dependency>

实现如下:

public class AhoCorasickReplacer implements Replacer {private final Trie trie;public AhoCorasickReplacer(String keyWords) {Trie.TrieBuilder builder = Trie.builder().ignoreOverlaps().onlyWholeWords();for (String keyword : keyWords.split(";")) {builder.addKeyword(keyword);}this.trie = builder.build();}@Overridepublic String replaceKeywords(String text) {StringBuilder result = new StringBuilder();int lastEnd = 0;for (Emit emit : trie.parseText(text)) {result.append(text, lastEnd, emit.getStart());lastEnd = emit.getEnd() + 1;}result.append(text.substring(lastEnd));return result.toString();}
}

这适用于关键词很多的高性能场景,是实际项目中非常推荐的方式。


4 自己手写 Trie 树实现替换

Trie 树(字典树)是一种结构简单的前缀树,可以快速查找前缀匹配的字符串。

图片位置(插图说明 Trie 树结构)

比如:

  • root → c → a → t 代表单词 “cat”
  • root → d → o → g 代表单词 “dog”

我们实现的代码只做“替换”功能,性能非常好:

public class TrieKeywordReplacer implements Replacer {private final Trie trie;public TrieKeywordReplacer(String keyWords) {trie = new Trie();for (String s : keyWords.split(";")) {trie.insert(s);}}@Overridepublic String replaceKeywords(String text) {return trie.replaceKeywords(text, "");}static class Trie {private final TrieNode root = new TrieNode();public void insert(String word) {TrieNode node = root;for (char c : word.toCharArray()) {node.children.putIfAbsent(c, new TrieNode());node = node.children.get(c);}node.isEndOfWord = true;}public String replaceKeywords(String text, String replacement) {StringBuilder result = new StringBuilder();int i = 0;while (i < text.length()) {TrieNode node = root;int j = i, lastMatch = -1;while (j < text.length() && node.children.containsKey(text.charAt(j))) {node = node.children.get(text.charAt(j));if (node.isEndOfWord) lastMatch = j;j++;}if (lastMatch >= 0) {result.append(replacement);i = lastMatch + 1;} else {result.append(text.charAt(i++));}}return result.toString();}}static class TrieNode {Map<Character, TrieNode> children = new HashMap<>();boolean isEndOfWord = false;}
}

内存占用比较

替换类对象大小(单位:字节)
StrReplacer12,560
PatternReplacer21,592
TrieKeywordReplacer184,944
AhoCorasickReplacer253,896

可以看出,自己实现的 Trie 占用较小,AC 自动机功能更强,占用更多内存


性能测试结果(关键词数量 400,JDK 1.8)

场景StrReplacerPatternReplacerTrieKeywordReplacerAhoCorasickReplacer
单线程 1w 次21843 ns28846 ns532 ns727 ns
2线程,有1个命中关键词23444 ns39984 ns680 ns1157 ns
2线程,有20个命中关键词252738 ns114740 ns33900 ns113764 ns
2线程,无关键词匹配22248 ns9253 ns397 ns738 ns

总结

  • 如果关键词很少,用 String.replace 最方便。
  • 如果关键词较多,推荐使用 AC 自动机(如 AhoCorasick)或自己实现 Trie。
  • 自己实现的 Trie 替换性能最强,因为它只做了“替换”一件事,轻量、高效。

相关文章:

  • Web 服务架构与技术组件概述
  • 一个非常快速的 Latex 入门教程【Part 1】
  • 你怎么通过postman或者fidder或者JMeter来获取到token,然后后面的请求怎么使用token
  • 【金仓数据库征文】金仓数据库:国产化浪潮下的技术突破与行业实践
  • PowerShell 查询及刷新环境变量
  • 一种用于加密代理流量检测的轻量级深度学习方法
  • C语言数据类型全面解析:从入门到精通
  • 详解 LeetCode 第 242 题 - 有效的字母组
  • DeepSeek智能时空数据分析(三):专业级地理数据可视化赏析-《杭州市国土空间总体规划(2021-2035年)》
  • 宁德时代25年时代长安动力电池社招入职测评SHL题库Verify测评语言理解数字推理真题
  • Matlab数字信号处理——小波阈值法去噪分析系统
  • spreadsheet 之websheet
  • Python在AI虚拟教学视频开发中的核心技术与前景展望
  • JUC多线程:读写锁
  • Pycharm(十六)面向对象进阶
  • python21-循环小作业
  • YOLOv8环境安装(超细全过程)
  • LeetCode -- Flora -- edit 2025-04-25
  • C++入侵检测与网络攻防之暴力破解
  • 项目笔记1:通用 Service的常见方法
  • 为何不当教练转型高校管理岗?苏炳添曾吐露过真实的想法
  • 拉卡拉一季度净利约1亿降超五成,去年净利3.5亿降逾23%
  • 鸿蒙智行八大车型亮相上海车展,余承东拉上三家车企老总“直播推销”
  • 国家发改委:更大力度、更实举措促进民营经济高质量发展
  • 大卫·第艾维瑞谈历史学与社会理论③丨尼古拉斯·卢曼与历史研究
  • 荣盛发展:拟以酒店、代建等轻资产板块业务搭建平台,并以其股权实施债务重组