Clojure Persistent Vector

05 Jan 2013

有关Clojure中的PersistentVector的记录,主要的参考来源是 http://blog.higher-order.net/2009/02/01/understanding-clojures-persistentvector-implementation/

自己感冒头晕,昨晚看American Horror Story又没有休息好. 于是就脑抽点了clojure的源码实现看,wc -l排序完,就想着先看个802行的吧.

$ wc -l * | sort -n
     13 Sequential.java
     14 IRecord.java
     14 IType.java
     15 IPending.java
     16 Fn.java
     16 IndexedSeq.java
     16 IPersistentList.java
     17 IBlockingDeref.java
     17 IDeref.java
     17 IEditableCollection.java
     17 IHashEq.java
     17 IKeywordLookup.java
     17 IMeta.java
     17 MapEquivalence.java
     17 Reversible.java
     17 Seqable.java
     18 Counted.java
     18 IObj.java
     18 IReference.java
     18 ITransientAssociative.java
     18 Settable.java
     19 Associative.java
     19 ILookup.java
     19 ILookupSite.java
     19 ILookupThunk.java
     19 IMapEntry.java
     19 Indexed.java
     19 IPersistentSet.java
     19 IPersistentStack.java
     19 IReduce.java
     19 ITransientSet.java
     19 Named.java
     20 IChunk.java
     20 IExceptionInfo.java
     20 IPersistentVector.java
     20 ITransientCollection.java
     20 ITransientVector.java
     21 IProxy.java
     22 Box.java
     22 ITransientMap.java
     22 Repl.java
     22 Script.java
     23 IChunkedSeq.java
     23 IPersistentCollection.java
     23 IPersistentMap.java
     23 Reduced.java
     25 Sorted.java
     26 Binding.java
     27 IRef.java
     29 ISeq.java
     32 ArityException.java
     33 SeqEnumeration.java
     34 LazilyPersistentVector.java
     35 Obj.java
     37 ChunkBuffer.java
     40 AReference.java
     40 MapEntry.java
     41 SeqIterator.java
     42 Delay.java
     42 ExceptionInfo.java
     54 ATransientSet.java
     54 StringSeq.java
     55 Cons.java
     57 KeywordLookupSite.java
     64 Range.java
     66 AFunction.java
     67 ChunkedCons.java
     69 ArrayChunk.java
     72 ProxyHandler.java
     75 FnLoaderThunk.java
     75 IteratorSeq.java
     76 DynamicClassLoader.java
     78 EnumerationSeq.java
     78 Ratio.java
     86 ATransientMap.java
     88 MethodImplCache.java
     89 XMLHandler.java
     90 PersistentTreeSet.java
     95 LineNumberingPushbackReader.java
     98 Compile.java
    104 Atom.java
    107 ARef.java
    128 PersistentHashSet.java
    133 Symbol.java
    134 Intrinsics.java
    149 AMapEntry.java
    158 APersistentSet.java
    174 BigInt.java
    197 TransactionalHashMap.java
    197 Util.java
    233 PersistentStructMap.java
    243 Namespace.java
    249 Keyword.java
    256 ASeq.java
    256 LazySeq.java
    287 Agent.java
    294 PersistentQueue.java
    311 PersistentList.java
    379 Ref.java
    395 APersistentMap.java
    436 PersistentArrayMap.java
    439 AFn.java
    449 IFn.java
    513 Reflector.java
    554 Var.java
    569 APersistentVector.java
    584 MultiFn.java
    645 LockingTransaction.java
    661 ArraySeq.java
    802 PersistentVector.java
   1029 PersistentTreeMap.java
   1180 PersistentHashMap.java
   1275 LispReader.java
   2234 RT.java
   4014 Numbers.java
   4104 RestFn.java
   8420 Compiler.java
  35118 总用量

1 查找

在文件里面梦游,看到个眼熟的 nth 于是就仔细看看,位操作是每个人的噩梦,但是还有前面的arrayFor没看,然后发现噩梦都是一个连一个的.

public Object[] arrayFor(int i){
    if(i >= 0 && i < cnt)
        {
            if(i >= tailoff())
                return tail;
            Node node = root;
            for(int level = shift; level > 0; level -= 5)
                node = (Node) node.array[(i >>> level) & 0x01f];
            return node.array;
        }
    throw new IndexOutOfBoundsException();
}

public Object nth(int i){
    Object[] node = arrayFor(i);
    return node[i & 0x01f];
}

自然不清楚为什么要取后五位.于是Google,找到了参考.因为数据结构可以理解为32叉树,所以后五位自然是找到最底层后的32个叶子节点后,根据后5位确定叶子.同理,上面的每次level为5,自然也是根据深度一步步往下找,shift大小自然就是树的深度相关,参考说是具体是5*(h+1),说是就是吧.Node的数据实际就是一个长度为32的array了.

static class Node implements Serializable {
    transient final AtomicReference<Thread> edit;
    final Object[] array;

    Node(AtomicReference<Thread> edit, Object[] array){
        this.edit = edit;
        this.array = array;
    }

    Node(AtomicReference<Thread> edit){
        this.edit = edit;
        this.array = new Object[32];
    }
}

然后突然发现还有 >>>>> 两种,前面的是unsigned后面的是signed,说明地址: http://docs.oracle.com/javase/tutorial/java/nutsandbolts/opsummary.html

final private int tailoff(){
    if(cnt < 32)
        return 0;
    return ((cnt-1) >>> 5) << 5;
}

这里相当于检查的是length/32的int值再*32……其实就是数据里面还有个tail,没凑够32个就先存这里.查找增加什么操作的,都需要考虑这个tail.

后面的assocN理解就自然了.

2 cons

public PersistentVector cons(Object val){
      int i = cnt;
      //room in tail?
      //      if(tail.length < 32)
      //这里还是tail的特殊处理,tail的长度没有32,就先放tail里面.这里好理解.
      if(cnt - tailoff() < 32)
          {
              Object[] newTail = new Object[tail.length + 1];
              System.arraycopy(tail, 0, newTail, 0, tail.length);
              newTail[tail.length] = val;
              return new PersistentVector(meta(), cnt + 1, shift, root, newTail);
          }
      //full tail, push into tree 如前所述,满了32.
      Node newroot;
      Node tailnode = new Node(root.edit,tail);
      int newshift = shift;
      //overflow root?下面的一坨操作,要先看看pushTail和newPath
      //树全满的情况下,重新升高一层树,新的tailnode要放在另外新建的深度如旧树的一棵树上
      if((cnt >>> 5) > (1 << shift))
          {
              newroot = new Node(root.edit);
              newroot.array[0] = root;
              newroot.array[1] = newPath(root.edit,shift, tailnode);
              newshift += 5;
          }
      //树没有全满,那么就往下找,有空就插入,没空就建新的枝.
      else
          newroot = pushTail(shift, root, tailnode);
      //所有的增加都会直接保证加入位置的深度一致.因为只有叶子才存数据.
      return new PersistentVector(meta(), cnt + 1, newshift, newroot, new Object[]{val});
  }

  private Node pushTail(int level, Node parent, Node tailnode){
      //if parent is leaf, insert node,
      // else does it map to an existing child? -> nodeToInsert = pushNode one more level
      // else alloc new path
      //return  nodeToInsert placed in copy of parent
      //
      int subidx = ((cnt - 1) >>> level) & 0x01f;
      Node ret = new Node(parent.edit, parent.array.clone());
      Node nodeToInsert;
      //parent is leaf的情况
      if(level == 5)
          {
              nodeToInsert = tailnode;
          }
      else
          {
              Node child = (Node) parent.array[subidx];
              //第二个判断情况,子树是null就新建一个单分支的子树到底层.不是的话就一直找到底层,然后加入.
              nodeToInsert = (child != null)?
                  pushTail(level-5,child, tailnode)
                  :newPath(root.edit,level-5, tailnode);
          }
      ret.array[subidx] = nodeToInsert;
      return ret;
  }

  //一路新建单分支的子树
  private static Node newPath(AtomicReference<Thread> edit,int level, Node node){
      if(level == 0)
          return node;
      Node ret = new Node(edit);
      ret.array[0] = newPath(edit, level - 5, node);
      return ret;
  }

完成添加后,数据是没有拷贝的,只是在原来树的后面添加了元素,然后成为一个新的树.性能上自然没有太大差别.

3 pop

public PersistentVector pop(){
    if(cnt == 0)
        throw new IllegalStateException("Can't pop empty vector");
    if(cnt == 1)
        return EMPTY.withMeta(meta());
    //if(tail.length > 1)
    //以直接在tail里面pop的,很容易.
    if(cnt-tailoff() > 1)
        {
            Object[] newTail = new Object[tail.length - 1];
            System.arraycopy(tail, 0, newTail, 0, newTail.length);
            return new PersistentVector(meta(), cnt - 1, shift, root, newTail);
        }
    //不能的情况下,就从树里pop出来一个,当然,同时就需要把剩下的31个不完整的抽出来当做newtail.
    //为什么要以cnt-2开始,因为前面的判断是tail.length > 1,tail里面有可能还有一个孤零零的元素.
    Object[] newtail = arrayFor(cnt - 2);

    //以下的代码就是从树中删除newtail的过程了.

    //新树的root也有可能需要做变更.
    Node newroot = popTail(shift, root);
    int newshift = shift;
    //这里如果一路都是单枝,结果是一路都删除路径到root,应该其实就是长度为32/33的情况.
    if(newroot == null)
        {
            newroot = EMPTY_NODE;
        }
    //层次大于1,但是root的第二子树已经是空的话,就需要降低层次了.
    if(shift > 5 && newroot.array[1] == null)
        {
            newroot = (Node) newroot.array[0];
            newshift -= 5;
        }
    return new PersistentVector(meta(), cnt - 1, newshift, newroot, newtail);
}

//
private Node popTail(int level, Node node){
    //倒数第二个位置的元素在最底层的index
    int subidx = ((cnt-2) >>> level) & 0x01f;
    //一直往底层走
    if(level > 5)
        {
            //去下层, level-5, 以subidx所在的array开始.
            Node newchild = popTail(level - 5, (Node) node.array[subidx]);
            //上层依然是单枝,需要继续删除路径的情况
            if(newchild == null && subidx == 0)
                return null;
            else
                {
                    Node ret = new Node(root.edit, node.array.clone());
                    ret.array[subidx] = newchild;
                    return ret;
                }
        }
    //到达底层,位置却是0,这里到达的不是包含叶子的底层,而是最后一个树枝层.
    //到达底树枝层,正好是一个单枝,因为下一步叶子层的index是0,如果要提出来当tail,这个单枝就需要删除了.而且要一路删除到上层不再是单枝路径为止.
    else if(subidx == 0)
        return null;
    //到达底树枝层
    else
        {
            Node ret = new Node(root.edit, node.array.clone());
            //显然,直接将要pop出来的tail删掉了.
            ret.array[subidx] = null;
            return ret;
        }
}

这里pop影响的范围都会重新clone.都是新的,原来的数据则还是保持.基本可以简单的认为是写时复制了.

还有个APersistentVector的抽象类,不管了.