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