The prefix tree supports a single operation root, which returns the root node. The nodes
support the following operations: at to obtain a child node, value to obtain the
value at the current node, setValue to atomically set the value and incValue to
atomically increment the value.
The prefix tree is implemented as follows. The tree points to the root node. Each node points to a set of child nodes, where each child is associated with a key. Each node additionally holds an arity, which is the number of children, and the 64-bit value of that node.
The set of child nodes can be represented as null if the set is empty, an array-list if
the the set is small, or a hash table if the set is large. In all cases, the keys and the child
nodes are kept in separate arrays.
The at operation, which takes a node and a key, and returns the corresponding child node,
deserves an additional explanation. This operation creates an existing child associated with the
key, or atomically creates a new child, if there was no child for that key.
The at operation is implemented as follows. There is a fast-path, which executes when the
child node already exists, and there are no concurrent modifications at that node, and the
slow-path, which executes when the child does not exist or when there is a concurrent
modification. To ensure that the slow path does not obtain a monitor, the fast-path relies on a
seqlock. This is a lightweight read-write lock that consists of a single 64-bit counter,
whose value is even when the lock is in the read mode, and odd when the lock is in the write
mode. The lock can be held by any number of readers, but at most 1 writer at any time.
In the read-mode, the reader must verify that the value of the lock is even and that it did not change from the point when the read started until the point when the read ended, and additionally takes care that reading an invalid state does not crash the execution. If the read fails for either of these reasons, the reader proceeds to the write-mode. In the write-mode, the reader or the writer enters a heavy lock (i.e. monitor) and then increments the seqlock's value by one, does the modification, and then increments the seqlock's value by one to make it even again. The volatile access semantics of the seqlock's value, along with the fact that the node's data only "grows" over time, are the properties that ensure the correctness of this implementation.
| Modifier and Type | Class and Description |
|---|---|
| public static class | |
| pack-priv static interface |
| Modifier and Type | Field and Description |
|---|---|
| private static final long | |
| private static final double | |
| private static final int | |
| private static final int | |
| private static final int | |
| private final SeqLockPrefixTree. |
| Access | Constructor and Description |
|---|---|
| public |
| Modifier and Type | Method and Description |
|---|---|
| public SeqLockPrefixTree. |
| EMPTY_KEY | back to summary |
|---|---|
| private static final long EMPTY_KEY | |
| HASH_NODE_LOAD_FACTOR | back to summary |
|---|---|
| private static final double HASH_NODE_LOAD_FACTOR | |
| INITIAL_HASH_NODE_SIZE | back to summary |
|---|---|
| private static final int INITIAL_HASH_NODE_SIZE | |
| INITIAL_LINEAR_NODE_SIZE | back to summary |
|---|---|
| private static final int INITIAL_LINEAR_NODE_SIZE | |
| MAX_LINEAR_NODE_SIZE | back to summary |
|---|---|
| private static final int MAX_LINEAR_NODE_SIZE | |
| root | back to summary |
|---|---|
| private final SeqLockPrefixTree. | |
| SeqLockPrefixTree | back to summary |
|---|---|
| public SeqLockPrefixTree() Create new
| |
| root | back to summary |
|---|---|
| public SeqLockPrefixTree. The root node of the tree.
| |
| Modifier and Type | Field and Description |
|---|---|
| private volatile int | |
| private volatile SeqLockPrefixTree. | |
| private volatile long[] | |
| private volatile long | |
| private static final long |
| Access | Constructor and Description |
|---|---|
| private |
| Modifier and Type | Method and Description |
|---|---|
| private void | |
| private void | |
| private void | |
| public SeqLockPrefixTree. | Returns: The child with the given childKey.the key of the child. key)Get existing (or create if missing) child with the given key. |
| private synchronized <R> R | |
| private void | |
| private static SeqLockPrefixTree. | |
| private SeqLockPrefixTree. | |
| private void | |
| private static int | |
| public long | |
| private boolean | |
| public long | |
| public void | |
| public synchronized < The type of the context C> void | topDown(C
The context for the root of the tree currentContext, BiFunction<C, Long, C> A function defining how the context for children is created createContext, BiConsumer<C, Long> A function that consumes the nodes value consumeValue)Traverse the tree top-down while maintaining a context. |
| public String | toString()
Overrides java. Returns the String representation of the current value. |
| private synchronized SeqLockPrefixTree. | |
| public long |
| arity | back to summary |
|---|---|
| private volatile int arity | |
| children | back to summary |
|---|---|
| private volatile SeqLockPrefixTree. | |
| keys | back to summary |
|---|---|
| private volatile long[] keys | |
| seqlock | back to summary |
|---|---|
| private volatile long seqlock | |
| serialVersionUID | back to summary |
|---|---|
| private static final long serialVersionUID Hides java. | |
| Node | back to summary |
|---|---|
| private Node() | |
| addChildToHashNode | back to summary |
|---|---|
| private void addChildToHashNode(long key, SeqLockPrefixTree. | |
| addChildToLinearNode | back to summary |
|---|---|
private void addChildToLinearNode(long key, SeqLockPrefixTree.
| |
| addChildToNonFullHashNode | back to summary |
|---|---|
private void addChildToNonFullHashNode(long key, SeqLockPrefixTree.
| |
| at | back to summary |
|---|---|
| public SeqLockPrefixTree. Get existing (or create if missing) child with the given key.
| |
| bottomUp | back to summary |
|---|---|
private synchronized <R> R bottomUp(SeqLockPrefixTree.
| |
| convertToHashNode | back to summary |
|---|---|
| private void convertToHashNode() | |
| findChild | back to summary |
|---|---|
| private static SeqLockPrefixTree. | |
| findChildLockFree | back to summary |
|---|---|
| private SeqLockPrefixTree. | |
| growHash | back to summary |
|---|---|
| private void growHash() | |
| hash | back to summary |
|---|---|
| private static int hash(long key) | |
| incValue | back to summary |
|---|---|
| public long incValue() Increment value.
| |
| mustGrowHash | back to summary |
|---|---|
| private boolean mustGrowHash() | |
| seqlockValue | back to summary |
|---|---|
public long seqlockValue()
| |
| setValue | back to summary |
|---|---|
| public void setValue(long value) Set the value for the
| |
| topDown | back to summary |
|---|---|
| public synchronized <C> void topDown(C currentContext, BiFunction<C, Long, C> createContext, BiConsumer<C, Long> consumeValue) Traverse the tree top-down while maintaining a context. The context is a generic data structure corresponding to the depth of the traversal, i.e. given the currentContext and a createContext function, a new context is created for each visited child using the createContext function, starting with initialContext.
| |
| toString | back to summary |
|---|---|
| public String toString() Overrides java. Doc from java. Returns the String representation of the current value. | |
| tryAddChild | back to summary |
|---|---|
private synchronized SeqLockPrefixTree.
| |
| value | back to summary |
|---|---|
public long value()
| |
| Modifier and Type | Method and Description |
|---|---|
| public R |
| visit | back to summary |
|---|---|
| public R visit(SeqLockPrefixTree. | |