有关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的抽象类,不管了.