From 04aae4d146446f0add5e9d71da1fc44508d66da1 Mon Sep 17 00:00:00 2001 From: cmm <1580166554@qq.com> Date: Fri, 2 Aug 2024 18:03:26 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=AB=98=E6=95=88?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E5=99=A8=20AC=E8=87=AA=E5=8A=A8=E6=9C=BA?= =?UTF-8?q?=E4=BC=98=E5=8C=96fail=E8=B7=B3=E8=8A=82=E7=82=B9=E5=A4=9A?= =?UTF-8?q?=E6=AC=A1=E9=87=8D=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/text/finder/MultiStrFinder.java | 285 ++++++++++++++++++ .../text/replacer/HighMultiReplacerV2.java | 115 +++++++ 2 files changed, 400 insertions(+) create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/text/replacer/HighMultiReplacerV2.java diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java new file mode 100644 index 000000000..a090e87d7 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java @@ -0,0 +1,285 @@ +package org.dromara.hutool.core.text.finder; + +import java.util.*; + +/** + * 多字符串查询器 底层思路 使用 AC 自动机实现 + * @author cmm + * @date 2024/8/2 上午10:07 + */ +public class MultiStrFinder { + + /** 字符索引 **/ + protected final Map charIndex = new HashMap<>(); + + /** 全部字符数量 **/ + protected final int allCharSize; + + /** 根节点 **/ + protected final Node root; + + /** + * 全部节点数量 + */ + int nodeSize; + + /** + * 构建多字符串查询器 + * @param source + */ + protected MultiStrFinder(Collection source){ + /** 待匹配的字符串 **/ + final Set stringSst = new HashSet<>(); + + /** 所有字符 **/ + final Set charSet = new HashSet<>(); + for (String string : source) { + stringSst.add(string); + char[] charArray = string.toCharArray(); + for (char c : charArray) { + charSet.add(c); + } + } + allCharSize = charSet.size(); + int index = 0; + for (Character c : charSet) { + charIndex.put(c,index); + index ++; + } + + root = Node.createRoot(allCharSize); + + buildPrefixTree(stringSst); + buildFail(); + } + + /** + * 构建前缀树 + * @param stringSst 待匹配的字符串 + */ + protected void buildPrefixTree(Collection stringSst){ + /** 节点编号 根节点已经是0了 所以从 1开始编号 **/ + int nodeIndex = 1; + for (String string : stringSst) { + Node node = root; + char[] charArray = string.toCharArray(); + for (int i = 0; i < charArray.length; i++) { + char c = charArray[i]; + boolean addValue = node.addValue(c, nodeIndex, charIndex); + if(addValue){ + nodeIndex ++; + } + node = node.directRouter[getIndex(c)]; + } + node.setEnd(string); + } + nodeSize = nodeIndex; + } + + /** + * 构建 fail指针过程 + * 构建 directRouter 直接访问路由表 减少跳fail次数 直接跳 router 边 + */ + protected void buildFail(){ + LinkedList nodeQueue = new LinkedList<>(); + for (int i = 0; i < root.directRouter.length; i++) { + Node nextNode = root.directRouter[i]; + if(nextNode == null){ + root.directRouter[i] = root; + continue; + } + nextNode.fail = root; + nodeQueue.addLast(nextNode); + } + + /* 进行广度优先遍历 **/ + while (!nodeQueue.isEmpty()){ + Node parent = nodeQueue.removeFirst(); + /** + * 因为 使用了 charIndex 进行字符到下标的映射 i 可以直接认为就是对应字符 char + */ + for (int i = 0; i < parent.directRouter.length; i++) { + Node child = parent.directRouter[i]; + /* child 为 null 表示没有子节点 **/ + if(child == null){ + parent.directRouter[i] = parent.fail.directRouter[i]; + continue; + } + child.fail = parent.fail.directRouter[i]; + nodeQueue.addLast(child); + child.fail.failPre.add(child); + } + } + } + + /** + * 查询匹配的字符串 + * @param text 返回每个匹配的 字符串 value是字符首字母地址 + * @return + */ + public Map> findMatch(String text){ + HashMap> resultMap = new HashMap<>(); + /* 节点经过次数 放在方法内部声明变量 希望可以一个构建对象 进行多次匹配 */ + + char[] chars = text.toCharArray(); + Node currentNode = root; + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + Integer index = charIndex.get(c); + /* 找不到字符索引 认为一定不在匹配字符中存在 直接从根节点开始重新计算 */ + if(index == null){ + currentNode = root; + continue; + } + /* 进入下一跳 可能是正常吓一跳 也可能是fail加上后的 下一跳 */ + currentNode = currentNode.directRouter[index]; + /* 判断是否尾部节点 是尾节点 说明已经匹配到了完整的字符串 将匹配结果写入返回对象 */ + if(currentNode.isEnd){ + resultMap.computeIfAbsent(currentNode.tagetString, k -> new ArrayList<>()) + .add(i - currentNode.tagetString.length() + 1); + } + + } + + return resultMap; + } + + + /** + * 获取字符 下标 + * @param c + * @return + */ + protected int getIndex(char c){ + Integer i = charIndex.get(c); + if(i == null){ + return -1; + } + return i; + } + + + public static MultiStrFinder create(Collection source){ + return new MultiStrFinder(source); + } + + /** + * AC 自动机节点 + */ + protected static class Node { + /** 是否是字符串 尾节点 **/ + public boolean isEnd = false; + + /** 如果当前节点是尾节点 那么表示 匹配到的字符串 其他情况下 null **/ + public String tagetString; + + /** + * 失效节点 + */ + public Node fail; + + /** + * 直接路由表 + * 减少挑 fail过程 使用数组 + charIndex 希望库减少 hash复杂度和内存空间 + * 当初始化 stringSet 数量较大时 字符较多可以一定程度上减少 hashMap 底层实现带来的 内存开销 + * directRouter 大小为 全部字符数量 + */ + public Node[] directRouter; + + /** 节点编号 root 为 0 **/ + public int nodeIndex; + + /** 值 **/ + public char value; + + /** fail指针来源 **/ + public List failPre = new ArrayList<>(); + + public Node(){} + + /** + * 新增子节点 + * @param c 字符 + * @param nodeIndex 节点编号 + * @param charIndex 字符索引 + * @return 如果已经存在子节点 false 新增 ture + */ + public boolean addValue(char c, int nodeIndex ,Map charIndex){ + Integer index = charIndex.get(c); + Node node = directRouter[index]; + if(node != null){ + return false; + } + node = new Node(); + directRouter[index] = node; + node.nodeIndex = nodeIndex; + node.directRouter = new Node[directRouter.length]; + node.value = c; + return true; + } + + /** + * 标记当前节点为 字符串尾节点 + * @param string + */ + public void setEnd(String string){ + tagetString = string; + isEnd = true; + } + + /** + * 获取下一跳 + * @param c + * @param charIndex + * @return + */ + public Node getNext(char c,Map charIndex){ + Integer index = charIndex.get(c); + if(index == null){ + return null; + } + return directRouter[index]; + } + + /** + * 构建根节点 + * @param allCharSize + * @return + */ + public static Node createRoot(int allCharSize){ + Node node = new Node(); + node.nodeIndex = 0; + node.fail = node; + node.directRouter = new Node[allCharSize]; + return node; + } + + @Override + public String toString() { + return value + ":" + nodeIndex; + } + } + +} + + + + + + + + + + + + + + + + + + + + + + diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/replacer/HighMultiReplacerV2.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/replacer/HighMultiReplacerV2.java new file mode 100644 index 000000000..412f230f9 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/replacer/HighMultiReplacerV2.java @@ -0,0 +1,115 @@ +package org.dromara.hutool.core.text.replacer; + +import org.dromara.hutool.core.text.finder.MultiStrFinder; + +import java.util.*; + +/** + * 高效替换器,通过查找指定关键字,替换对应的值 + * 基于AC自动机算法实现,需要被替换的原字符串越大,替换的键值对越多,效率提升越明显 + *

+ * 注意: 如果需要被替换的关键字出现交叉,最先匹配中的关键字会被替换 + * 1、"abc","ab" 会优先替换"ab" + * 2、"abed","be" 会优先替换"abed" + * 3、"abc", "bc" 会优先替换"abc" + * + * @author newshiJ + * @date 2024/8/2 下午3:41 + */ +public class HighMultiReplacerV2 extends StrReplacer { + + private final AhoCorasickAutomaton ahoCorasickAutomaton; + + /** + * 构造 + * + * @param map key为需要被查找的字符串,value为对应的替换的值 + */ + public HighMultiReplacerV2(final Map map) { + ahoCorasickAutomaton = new AhoCorasickAutomaton(map); + } + + @Override + protected int replace(final CharSequence str, final int pos, final StringBuilder out) { + ahoCorasickAutomaton.replace(str, out); + return str.length(); + } + + @Override + public CharSequence apply(final CharSequence str) { + final StringBuilder builder = new StringBuilder(); + replace(str, 0, builder); + return builder; + } + + + /** + * AC 自动机 + */ + protected static class AhoCorasickAutomaton extends MultiStrFinder{ + protected final Map replaceMap; + + public AhoCorasickAutomaton(Map replaceMap){ + super(replaceMap.keySet()); + this.replaceMap = replaceMap; + } + + + public void replace(final CharSequence text, final StringBuilder stringBuilder){ + Node currentNode = root; + /* 临时字符串存储空间 **/ + StringBuilder temp = new StringBuilder(); + for (int i = 0; i < text.length(); i++) { + char ch = text.charAt(i); + Integer index = charIndex.get(ch); + /* 下一个字符在候选转换字符串中都不存在 ch字符一定不会被替换 */ + if(index < 0){ + /* 临时缓存空间中的数据写入到输出的 StringBuilder */ + if(temp.length() > 0){ + stringBuilder.append(temp); + /* 数据写入后清空临时空间 */ + temp.delete(0, temp.length()); + } + /* 将一个一定不会替换的字符 ch 写入输出 */ + stringBuilder.append(ch); + /* 匹配失败 将当前节点重新指向根节点 */ + currentNode = root; + continue; + } + + /* 这个逻辑分支表示 已经匹配到了下一跳 */ + currentNode = currentNode.directRouter[index]; + + /* 当前是root节点表示匹配中断 清理临时空间 写入到输出 */ + if(currentNode.nodeIndex == 0){ + if(temp.length() > 0){ + stringBuilder.append(temp); + /* 数据写入后清空临时空间 */ + temp.delete(0, temp.length()); + /* 当前情况表示该字符存在在候选转换字符中 但是前一个字符到这里是不存在路径 */ + stringBuilder.append(ch); + continue; + } + } + + /* 表示匹配到 现在进行字符串替换工作 */ + if(currentNode.isEnd){ + int length = currentNode.tagetString.length(); + /* 先清理匹配到的字符 最后一个字符未加入临时空间 */ + temp.delete(temp.length() - length + 1,length - 1); + if(temp.length() > 0){ + stringBuilder.append(temp); + } + /* 写入被替换的字符串 */ + stringBuilder.append(replaceMap.get(currentNode.tagetString)); + /* 因为字符串被替换过了 所以当前节点重新指向 root */ + currentNode = root; + continue; + } + + temp.append(ch); + } + } + } + +} From ff707a602f944d697dafd0a1851c89a61747e46d Mon Sep 17 00:00:00 2001 From: cmm <1580166554@qq.com> Date: Sat, 3 Aug 2024 07:01:11 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=83=A8=E5=88=86?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BB=A5=E5=8F=8A=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dromara/hutool/core/text/finder/MultiStrFinder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java index a090e87d7..f16b99bce 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java @@ -4,7 +4,7 @@ import java.util.*; /** * 多字符串查询器 底层思路 使用 AC 自动机实现 - * @author cmm + * @author newshiJ * @date 2024/8/2 上午10:07 */ public class MultiStrFinder { @@ -27,7 +27,7 @@ public class MultiStrFinder { * 构建多字符串查询器 * @param source */ - protected MultiStrFinder(Collection source){ + public MultiStrFinder(Collection source){ /** 待匹配的字符串 **/ final Set stringSst = new HashSet<>(); @@ -131,7 +131,7 @@ public class MultiStrFinder { currentNode = root; continue; } - /* 进入下一跳 可能是正常吓一跳 也可能是fail加上后的 下一跳 */ + /* 进入下一跳 可能是正常下一跳 也可能是fail加上后的 下一跳 */ currentNode = currentNode.directRouter[index]; /* 判断是否尾部节点 是尾节点 说明已经匹配到了完整的字符串 将匹配结果写入返回对象 */ if(currentNode.isEnd){ From 86f6b244602054b68debd05f98a90aa099cbff2c Mon Sep 17 00:00:00 2001 From: cmm <1580166554@qq.com> Date: Sat, 3 Aug 2024 07:04:13 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dromara/hutool/core/text/finder/MultiStrFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java index f16b99bce..283327c67 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java @@ -118,8 +118,8 @@ public class MultiStrFinder { * @return */ public Map> findMatch(String text){ - HashMap> resultMap = new HashMap<>(); /* 节点经过次数 放在方法内部声明变量 希望可以一个构建对象 进行多次匹配 */ + HashMap> resultMap = new HashMap<>(); char[] chars = text.toCharArray(); Node currentNode = root; From da2919ae7b29eaf32ea9bb5cded6d9391393035f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E8=B6=85?= Date: Sat, 3 Aug 2024 16:17:19 +0000 Subject: [PATCH 4/5] Update hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java --- .../org/dromara/hutool/core/text/finder/MultiStrFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java index 283327c67..033a6ee6e 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java @@ -29,7 +29,7 @@ public class MultiStrFinder { */ public MultiStrFinder(Collection source){ /** 待匹配的字符串 **/ - final Set stringSst = new HashSet<>(); + final Set stringSet = new HashSet<>(); /** 所有字符 **/ final Set charSet = new HashSet<>(); From 52bdf53efb66c4b6de914b56771b93c023a4a36b Mon Sep 17 00:00:00 2001 From: cmm <1580166554@qq.com> Date: Sun, 4 Aug 2024 00:17:37 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E5=BD=A2=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/text/finder/MultiStrFinder.java | 58 +++++++++---------- .../text/replacer/HighMultiReplacerV2.java | 28 ++++----- 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java index 283327c67..9f2155881 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/finder/MultiStrFinder.java @@ -9,18 +9,16 @@ import java.util.*; */ public class MultiStrFinder { - /** 字符索引 **/ + // 字符索引 protected final Map charIndex = new HashMap<>(); - /** 全部字符数量 **/ + // 全部字符数量 protected final int allCharSize; - /** 根节点 **/ + // 根节点 protected final Node root; - /** - * 全部节点数量 - */ + // 全部节点数量 int nodeSize; /** @@ -28,13 +26,13 @@ public class MultiStrFinder { * @param source */ public MultiStrFinder(Collection source){ - /** 待匹配的字符串 **/ - final Set stringSst = new HashSet<>(); + // 待匹配的字符串 + final Set stringSet = new HashSet<>(); - /** 所有字符 **/ + // 所有字符 final Set charSet = new HashSet<>(); for (String string : source) { - stringSst.add(string); + stringSet.add(string); char[] charArray = string.toCharArray(); for (char c : charArray) { charSet.add(c); @@ -49,7 +47,7 @@ public class MultiStrFinder { root = Node.createRoot(allCharSize); - buildPrefixTree(stringSst); + buildPrefixTree(stringSet); buildFail(); } @@ -58,7 +56,7 @@ public class MultiStrFinder { * @param stringSst 待匹配的字符串 */ protected void buildPrefixTree(Collection stringSst){ - /** 节点编号 根节点已经是0了 所以从 1开始编号 **/ + // 节点编号 根节点已经是0了 所以从 1开始编号 int nodeIndex = 1; for (String string : stringSst) { Node node = root; @@ -92,15 +90,13 @@ public class MultiStrFinder { nodeQueue.addLast(nextNode); } - /* 进行广度优先遍历 **/ + // 进行广度优先遍历 while (!nodeQueue.isEmpty()){ Node parent = nodeQueue.removeFirst(); - /** - * 因为 使用了 charIndex 进行字符到下标的映射 i 可以直接认为就是对应字符 char - */ + // 因为 使用了 charIndex 进行字符到下标的映射 i 可以直接认为就是对应字符 char for (int i = 0; i < parent.directRouter.length; i++) { Node child = parent.directRouter[i]; - /* child 为 null 表示没有子节点 **/ + // child 为 null 表示没有子节点 if(child == null){ parent.directRouter[i] = parent.fail.directRouter[i]; continue; @@ -118,7 +114,7 @@ public class MultiStrFinder { * @return */ public Map> findMatch(String text){ - /* 节点经过次数 放在方法内部声明变量 希望可以一个构建对象 进行多次匹配 */ + // 节点经过次数 放在方法内部声明变量 希望可以一个构建对象 进行多次匹配 HashMap> resultMap = new HashMap<>(); char[] chars = text.toCharArray(); @@ -126,14 +122,14 @@ public class MultiStrFinder { for (int i = 0; i < chars.length; i++) { char c = chars[i]; Integer index = charIndex.get(c); - /* 找不到字符索引 认为一定不在匹配字符中存在 直接从根节点开始重新计算 */ + // 找不到字符索引 认为一定不在匹配字符中存在 直接从根节点开始重新计算 if(index == null){ currentNode = root; continue; } - /* 进入下一跳 可能是正常下一跳 也可能是fail加上后的 下一跳 */ + // 进入下一跳 可能是正常下一跳 也可能是fail加上后的 下一跳 currentNode = currentNode.directRouter[index]; - /* 判断是否尾部节点 是尾节点 说明已经匹配到了完整的字符串 将匹配结果写入返回对象 */ + // 判断是否尾部节点 是尾节点 说明已经匹配到了完整的字符串 将匹配结果写入返回对象 if(currentNode.isEnd){ resultMap.computeIfAbsent(currentNode.tagetString, k -> new ArrayList<>()) .add(i - currentNode.tagetString.length() + 1); @@ -167,15 +163,13 @@ public class MultiStrFinder { * AC 自动机节点 */ protected static class Node { - /** 是否是字符串 尾节点 **/ + // 是否是字符串 尾节点 public boolean isEnd = false; - /** 如果当前节点是尾节点 那么表示 匹配到的字符串 其他情况下 null **/ + // 如果当前节点是尾节点 那么表示 匹配到的字符串 其他情况下 null public String tagetString; - /** - * 失效节点 - */ + //失效节点 public Node fail; /** @@ -186,13 +180,13 @@ public class MultiStrFinder { */ public Node[] directRouter; - /** 节点编号 root 为 0 **/ + // 节点编号 root 为 0 public int nodeIndex; - /** 值 **/ + // 值 public char value; - /** fail指针来源 **/ + // fail指针来源 public List failPre = new ArrayList<>(); public Node(){} @@ -229,8 +223,8 @@ public class MultiStrFinder { /** * 获取下一跳 - * @param c - * @param charIndex + * @param c 字符 + * @param charIndex 字符索引 * @return */ public Node getNext(char c,Map charIndex){ @@ -243,7 +237,7 @@ public class MultiStrFinder { /** * 构建根节点 - * @param allCharSize + * @param allCharSize 全部字符数量 * @return */ public static Node createRoot(int allCharSize){ diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/replacer/HighMultiReplacerV2.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/replacer/HighMultiReplacerV2.java index 412f230f9..3f79eae24 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/replacer/HighMultiReplacerV2.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/replacer/HighMultiReplacerV2.java @@ -57,52 +57,52 @@ public class HighMultiReplacerV2 extends StrReplacer { public void replace(final CharSequence text, final StringBuilder stringBuilder){ Node currentNode = root; - /* 临时字符串存储空间 **/ + // 临时字符串存储空间 StringBuilder temp = new StringBuilder(); for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i); Integer index = charIndex.get(ch); - /* 下一个字符在候选转换字符串中都不存在 ch字符一定不会被替换 */ + // 下一个字符在候选转换字符串中都不存在 ch字符一定不会被替换 if(index < 0){ - /* 临时缓存空间中的数据写入到输出的 StringBuilder */ + // 临时缓存空间中的数据写入到输出的 StringBuilder if(temp.length() > 0){ stringBuilder.append(temp); - /* 数据写入后清空临时空间 */ + // 数据写入后清空临时空间 temp.delete(0, temp.length()); } - /* 将一个一定不会替换的字符 ch 写入输出 */ + // 将一个一定不会替换的字符 ch 写入输出 stringBuilder.append(ch); - /* 匹配失败 将当前节点重新指向根节点 */ + // 匹配失败 将当前节点重新指向根节点 currentNode = root; continue; } - /* 这个逻辑分支表示 已经匹配到了下一跳 */ + // 这个逻辑分支表示 已经匹配到了下一跳 currentNode = currentNode.directRouter[index]; - /* 当前是root节点表示匹配中断 清理临时空间 写入到输出 */ + // 当前是root节点表示匹配中断 清理临时空间 写入到输出 if(currentNode.nodeIndex == 0){ if(temp.length() > 0){ stringBuilder.append(temp); - /* 数据写入后清空临时空间 */ + // 数据写入后清空临时空间 temp.delete(0, temp.length()); - /* 当前情况表示该字符存在在候选转换字符中 但是前一个字符到这里是不存在路径 */ + // 当前情况表示该字符存在在候选转换字符中 但是前一个字符到这里是不存在路径 stringBuilder.append(ch); continue; } } - /* 表示匹配到 现在进行字符串替换工作 */ + // 表示匹配到 现在进行字符串替换工作 if(currentNode.isEnd){ int length = currentNode.tagetString.length(); - /* 先清理匹配到的字符 最后一个字符未加入临时空间 */ + // 先清理匹配到的字符 最后一个字符未加入临时空间 temp.delete(temp.length() - length + 1,length - 1); if(temp.length() > 0){ stringBuilder.append(temp); } - /* 写入被替换的字符串 */ + // 写入被替换的字符串 stringBuilder.append(replaceMap.get(currentNode.tagetString)); - /* 因为字符串被替换过了 所以当前节点重新指向 root */ + // 因为字符串被替换过了 所以当前节点重新指向 root currentNode = root; continue; }