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 Deque<String> treePath = 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);
}
|