2021SC@SDUSC
前言本节介绍PathTrie类,它是zookeeper中对字典树的实现(传统的字典树一个节点或者一条边存储的是一个字符,这里则是对路径分割后的字符串),在DataTree类中用于记录配额节点。
PathTrie成员变量:
//该类的日志 private static final Logger LOG = LoggerFactory.getLogger(PathTrie.class); //根节点 private final TrieNode rootNode; //这里用了java的并发包提供的读写锁ReentrantReadWriteLock,且构造参数为true,使用的是公平锁 private final ReadWriteLock lock = new ReentrantReadWriteLock(true); //读取 *** 作的锁 private final Lock readLock = lock.readLock(); //写入 *** 作的锁 private final Lock writeLock = lock.writeLock();
构造函数:
//创建根节点 public PathTrie() { //根节点没有父节点,参数为null,存储的值为"/" this.rootNode = new TrieNode(null, "/"); }
addPath:
//将对应路径加入字典树,路径都是相对根节点而言的 public void addPath(final String path) { //path不能为null Objects.requireNonNull(path, "Path cannot be null"); //path不能为空字符串 if (path.length() == 0) { throw new IllegalArgumentException("Invalid path: " + path); } //将路径切割 final String[] pathComponents = split(path); //使用写锁 writeLock.lock(); try { TrieNode parent = rootNode; //遍历刚才切割后得到的string数组 for (final String part : pathComponents) { //如果已经有节点,那么就直接使用,如果没有,就创建对应节点 TrieNode child = parent.getChild(part); if (child == null) { child = new TrieNode(parent, part); parent.addChild(part, child); } //递归,自顶向下安排节点 parent = child; } //设置属性为true,字典树中一个节点属性为true,表明该“单词”存在(在此处则是指路径存在) parent.setProperty(true); } finally { //释放写锁 writeLock.unlock(); } }
deletePath:
//从字典树中删除路径,路径均是相对根节点而言的 public void deletePath(final String path) { //path不能为null Objects.requireNonNull(path, "Path cannot be null"); //path不能为空字符串 if (path.length() == 0) { throw new IllegalArgumentException("Invalid path: " + path); } //对路径切割得到字符串数组 final String[] pathComponents = split(path); //使用写锁 writeLock.lock(); try { TrieNode parent = rootNode; //遍历对路径切割后得到的字符串数组 for (final String part : pathComponents) { if (parent.getChild(part) == null) { //如果在树中找不到对应节点,表明树中根本没有存储这个路径,直接返回 return; } //字典树中查找“单词”是否存在的基本 *** 作,从根节点开始一步步向下看构成单词的字符是否存在,在此处“单词”指“路径”,而“构成单词的字符”就是“对路径切割后的字符串数组” parent = parent.getChild(part); LOG.debug("{}", parent); } //获取“单词的最后一个字符”对应节点的父节点 final TrieNode realParent = parent.getParent(); //字典树意义上的删除,节点的property属性置为false(表明字典树中没有这个路径),但节点未必删除,因为该节点可能还有子节点(即该节点不是叶子节点) realParent.deleteChild(parent.getValue()); } finally { //释放写锁 writeLock.unlock(); } }
existsNode:
//判断字典树是否存在指定路径,路径是相对根节点而言的 public boolean existsNode(final String path) { //同样地,路径不能是null,不能是空字符串 Objects.requireNonNull(path, "Path cannot be null"); if (path.length() == 0) { throw new IllegalArgumentException("Invalid path: " + path); } //路径字符串切割 final String[] pathComponents = split(path); //使用读锁,这里不涉及写 *** 作,可以共享 readLock.lock(); try { //这里和deletePath方法中的过程是一致的,字典树判断指定“单词”是否存在的典型方法 TrieNode parent = rootNode; for (final String part : pathComponents) { if (parent.getChild(part) == null) { return false; } parent = parent.getChild(part); LOG.debug("{}", parent); } } finally { //释放读锁 readLock.unlock(); } return true; }
findMaxPrefix:
//返回指定路径的最大前缀(需要在字典树中是存在的),路径都是相对根节点而言的 public String findMaxPrefix(final String path) { //路径不能为null Objects.requireNonNull(path, "Path cannot be null"); //对路径切割 final String[] pathComponents = split(path); //不涉及写 *** 作,使用读锁 readLock.lock(); try { TrieNode parent = rootNode; TrieNode deepestPropertyNode = null; //对字典树做“查找单词是否存在”的遍历,若某个“字符”不存在,则退出循环,在这个过程中记录遍历过程中符合property为true(即在字典树中存在)的最后访问的节点 for (final String element : pathComponents) { parent = parent.getChild(element); if (parent == null) { LOG.debug("{}", element); break; } if (parent.hasProperty()) { deepestPropertyNode = parent; } } //如果遍历过程中没有property为true的节点,返回“/” if (deepestPropertyNode == null) { return "/"; } final DequetreePath = new ArrayDeque<>(); TrieNode node = deepestPropertyNode; //从deepestPropertyNode向根节点回溯,从而打印出最大前缀路径 while (node != this.rootNode) { //插入头部 treePath.offerFirst(node.getValue()); node = node.parent; } return "/" + String.join("/", treePath); } finally { //释放读锁 readLock.unlock(); } }
clear:
//清空节点 public void clear() { //使用写锁 writeLock.lock(); try { //根节点的子节点都清空 rootNode.getChildren().clear(); } finally { //释放写锁 writeLock.unlock(); } }
split:
//用于对路径字符串做切割,该方法保证了一定的健壮性,可以处理路径中含空白符的情况 private static String[] split(final String path){ return Stream.of(path.split("/")) .filter(t -> !t.trim().isEmpty()) .toArray(String[]::new); }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)