zookeeper源码解析(八)

2021SC@SDUSC

前言

本节介绍PathTrie类,它是zookeeper中对字典树的实现(传统的字典树一个节点或者一条边存储的是一个字符,这里则是对路径分割后的字符串),在DataTree类中用于记录配额节点。

直观理解

一般意义的字典树形式如下(注意,在这里zookeeper所用字典树一条边不再是存储只一个字符,而是路径中”/“之间的部分)

 

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);
     }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值