/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * This file is available under and governed by the GNU General Public * License version 2 only, as published by the Free Software Foundation. * However, the following notice accompanied the original version of this * file: * * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ */ package java.util.concurrent; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.io.Serializable; import java.util.AbstractCollection; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NavigableSet; import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedMap; import java.util.Spliterator; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.concurrent.atomic.LongAdder; /** * A scalable concurrent {@link ConcurrentNavigableMap} implementation. * The map is sorted according to the {@linkplain Comparable natural * ordering} of its keys, or by a {@link Comparator} provided at map * creation time, depending on which constructor is used. * *

This class implements a concurrent variant of SkipLists * providing expected average log(n) time cost for the * {@code containsKey}, {@code get}, {@code put} and * {@code remove} operations and their variants. Insertion, removal, * update, and access operations safely execute concurrently by * multiple threads. * *

Iterators and spliterators are * weakly consistent. * *

Ascending key ordered views and their iterators are faster than * descending ones. * *

All {@code Map.Entry} pairs returned by methods in this class * and its views represent snapshots of mappings at the time they were * produced. They do not support the {@code Entry.setValue} * method. (Note however that it is possible to change mappings in the * associated map using {@code put}, {@code putIfAbsent}, or * {@code replace}, depending on exactly which effect you need.) * *

Beware that bulk operations {@code putAll}, {@code equals}, * {@code toArray}, {@code containsValue}, and {@code clear} are * not guaranteed to be performed atomically. For example, an * iterator operating concurrently with a {@code putAll} operation * might view only some of the added elements. * *

This class and its views and iterators implement all of the * optional methods of the {@link Map} and {@link Iterator} * interfaces. Like most other concurrent collections, this class does * not permit the use of {@code null} keys or values because some * null return values cannot be reliably distinguished from the absence of * elements. * *

This class is a member of the * * Java Collections Framework. * * @author Doug Lea * @param the type of keys maintained by this map * @param the type of mapped values * @since 1.6 */ public class ConcurrentSkipListMap extends AbstractMap implements ConcurrentNavigableMap, Cloneable, Serializable { /* * This class implements a tree-like two-dimensionally linked skip * list in which the index levels are represented in separate * nodes from the base nodes holding data. There are two reasons * for taking this approach instead of the usual array-based * structure: 1) Array based implementations seem to encounter * more complexity and overhead 2) We can use cheaper algorithms * for the heavily-traversed index lists than can be used for the * base lists. Here's a picture of some of the basics for a * possible list with 2 levels of index: * * Head nodes Index nodes * +-+ right +-+ +-+ * |2|---------------->| |--------------------->| |->null * +-+ +-+ +-+ * | down | | * v v v * +-+ +-+ +-+ +-+ +-+ +-+ * |1|----------->| |->| |------>| |----------->| |------>| |->null * +-+ +-+ +-+ +-+ +-+ +-+ * v | | | | | * Nodes next v v v v v * +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ * | |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null * +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ * * The base lists use a variant of the HM linked ordered set * algorithm. See Tim Harris, "A pragmatic implementation of * non-blocking linked lists" * http://www.cl.cam.ac.uk/~tlh20/publications.html and Maged * Michael "High Performance Dynamic Lock-Free Hash Tables and * List-Based Sets" * http://www.research.ibm.com/people/m/michael/pubs.htm. The * basic idea in these lists is to mark the "next" pointers of * deleted nodes when deleting to avoid conflicts with concurrent * insertions, and when traversing to keep track of triples * (predecessor, node, successor) in order to detect when and how * to unlink these deleted nodes. * * Rather than using mark-bits to mark list deletions (which can * be slow and space-intensive using AtomicMarkedReference), nodes * use direct CAS'able next pointers. On deletion, instead of * marking a pointer, they splice in another node that can be * thought of as standing for a marked pointer (see method * unlinkNode). Using plain nodes acts roughly like "boxed" * implementations of marked pointers, but uses new nodes only * when nodes are deleted, not for every link. This requires less * space and supports faster traversal. Even if marked references * were better supported by JVMs, traversal using this technique * might still be faster because any search need only read ahead * one more node than otherwise required (to check for trailing * marker) rather than unmasking mark bits or whatever on each * read. * * This approach maintains the essential property needed in the HM * algorithm of changing the next-pointer of a deleted node so * that any other CAS of it will fail, but implements the idea by * changing the pointer to point to a different node (with * otherwise illegal null fields), not by marking it. While it * would be possible to further squeeze space by defining marker * nodes not to have key/value fields, it isn't worth the extra * type-testing overhead. The deletion markers are rarely * encountered during traversal, are easily detected via null * checks that are needed anyway, and are normally quickly garbage * collected. (Note that this technique would not work well in * systems without garbage collection.) * * In addition to using deletion markers, the lists also use * nullness of value fields to indicate deletion, in a style * similar to typical lazy-deletion schemes. If a node's value is * null, then it is considered logically deleted and ignored even * though it is still reachable. * * Here's the sequence of events for a deletion of node n with * predecessor b and successor f, initially: * * +------+ +------+ +------+ * ... | b |------>| n |----->| f | ... * +------+ +------+ +------+ * * 1. CAS n's value field from non-null to null. * Traversals encountering a node with null value ignore it. * However, ongoing insertions and deletions might still modify * n's next pointer. * * 2. CAS n's next pointer to point to a new marker node. * From this point on, no other nodes can be appended to n. * which avoids deletion errors in CAS-based linked lists. * * +------+ +------+ +------+ +------+ * ... | b |------>| n |----->|marker|------>| f | ... * +------+ +------+ +------+ +------+ * * 3. CAS b's next pointer over both n and its marker. * From this point on, no new traversals will encounter n, * and it can eventually be GCed. * +------+ +------+ * ... | b |----------------------------------->| f | ... * +------+ +------+ * * A failure at step 1 leads to simple retry due to a lost race * with another operation. Steps 2-3 can fail because some other * thread noticed during a traversal a node with null value and * helped out by marking and/or unlinking. This helping-out * ensures that no thread can become stuck waiting for progress of * the deleting thread. * * Skip lists add indexing to this scheme, so that the base-level * traversals start close to the locations being found, inserted * or deleted -- usually base level traversals only traverse a few * nodes. This doesn't change the basic algorithm except for the * need to make sure base traversals start at predecessors (here, * b) that are not (structurally) deleted, otherwise retrying * after processing the deletion. * * Index levels are maintained using CAS to link and unlink * successors ("right" fields). Races are allowed in index-list * operations that can (rarely) fail to link in a new index node. * (We can't do this of course for data nodes.) However, even * when this happens, the index lists correctly guide search. * This can impact performance, but since skip lists are * probabilistic anyway, the net result is that under contention, * the effective "p" value may be lower than its nominal value. * * Index insertion and deletion sometimes require a separate * traversal pass occurring after the base-level action, to add or * remove index nodes. This adds to single-threaded overhead, but * improves contended multithreaded performance by narrowing * interference windows, and allows deletion to ensure that all * index nodes will be made unreachable upon return from a public * remove operation, thus avoiding unwanted garbage retention. * * Indexing uses skip list parameters that maintain good search * performance while using sparser-than-usual indices: The * hardwired parameters k=1, p=0.5 (see method doPut) mean that * about one-quarter of the nodes have indices. Of those that do, * half have one level, a quarter have two, and so on (see Pugh's * Skip List Cookbook, sec 3.4), up to a maximum of 62 levels * (appropriate for up to 2^63 elements). The expected total * space requirement for a map is slightly less than for the * current implementation of java.util.TreeMap. * * Changing the level of the index (i.e, the height of the * tree-like structure) also uses CAS. Creation of an index with * height greater than the current level adds a level to the head * index by CAS'ing on a new top-most head. To maintain good * performance after a lot of removals, deletion methods * heuristically try to reduce the height if the topmost levels * appear to be empty. This may encounter races in which it is * possible (but rare) to reduce and "lose" a level just as it is * about to contain an index (that will then never be * encountered). This does no structural harm, and in practice * appears to be a better option than allowing unrestrained growth * of levels. * * This class provides concurrent-reader-style memory consistency, * ensuring that read-only methods report status and/or values no * staler than those holding at method entry. This is done by * performing all publication and structural updates using * (volatile) CAS, placing an acquireFence in a few access * methods, and ensuring that linked objects are transitively * acquired via dependent reads (normally once) unless performing * a volatile-mode CAS operation (that also acts as an acquire and * release). This form of fence-hoisting is similar to RCU and * related techniques (see McKenney's online book * https://www.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html) * It minimizes overhead that may otherwise occur when using so * many volatile-mode reads. Using explicit acquireFences is * logistically easier than targeting particular fields to be read * in acquire mode: fences are just hoisted up as far as possible, * to the entry points or loop headers of a few methods. A * potential disadvantage is that these few remaining fences are * not easily optimized away by compilers under exclusively * single-thread use. It requires some care to avoid volatile * mode reads of other fields. (Note that the memory semantics of * a reference dependently read in plain mode exactly once are * equivalent to those for atomic opaque mode.) Iterators and * other traversals encounter each node and value exactly once. * Other operations locate an element (or position to insert an * element) via a sequence of dereferences. This search is broken * into two parts. Method findPredecessor (and its specialized * embeddings) searches index nodes only, returning a base-level * predecessor of the key. Callers carry out the base-level * search, restarting if encountering a marker preventing link * modification. In some cases, it is possible to encounter a * node multiple times while descending levels. For mutative * operations, the reported value is validated using CAS (else * retrying), preserving linearizability with respect to each * other. Others may return any (non-null) value holding in the * course of the method call. (Search-based methods also include * some useless-looking explicit null checks designed to allow * more fields to be nulled out upon removal, to reduce floating * garbage, but which is not currently done, pending discovery of * a way to do this with less impact on other operations.) * * To produce random values without interference across threads, * we use within-JDK thread local random support (via the * "secondary seed", to avoid interference with user-level * ThreadLocalRandom.) * * For explanation of algorithms sharing at least a couple of * features with this one, see Mikhail Fomitchev's thesis * (http://www.cs.yorku.ca/~mikhail/), Keir Fraser's thesis * (http://www.cl.cam.ac.uk/users/kaf24/), and Hakan Sundell's * thesis (http://www.cs.chalmers.se/~phs/). * * Notation guide for local variables * Node: b, n, f, p for predecessor, node, successor, aux * Index: q, r, d for index node, right, down. * Head: h * Keys: k, key * Values: v, value * Comparisons: c */ private static final long serialVersionUID = -8627078645895051609L; /** * The comparator used to maintain order in this map, or null if * using natural ordering. (Non-private to simplify access in * nested classes.) * @serial */ final Comparator comparator; /** Lazily initialized topmost index of the skiplist. */ private transient Index head; /** Lazily initialized element count */ private transient LongAdder adder; /** Lazily initialized key set */ private transient KeySet keySet; /** Lazily initialized values collection */ private transient Values values; /** Lazily initialized entry set */ private transient EntrySet entrySet; /** Lazily initialized descending map */ private transient SubMap descendingMap; /** * Nodes hold keys and values, and are singly linked in sorted * order, possibly with some intervening marker nodes. The list is * headed by a header node accessible as head.node. Headers and * marker nodes have null keys. The val field (but currently not * the key field) is nulled out upon deletion. */ static final class Node { final K key; // currently, never detached V val; Node next; Node(K key, V value, Node next) { this.key = key; this.val = value; this.next = next; } } /** * Index nodes represent the levels of the skip list. */ static final class Index { final Node node; // currently, never detached final Index down; Index right; Index(Node node, Index down, Index right) { this.node = node; this.down = down; this.right = right; } } /* ---------------- Utilities -------------- */ /** * Compares using comparator or natural ordering if null. * Called only by methods that have performed required type checks. */ @SuppressWarnings({"unchecked", "rawtypes"}) static int cpr(Comparator c, Object x, Object y) { return (c != null) ? c.compare(x, y) : ((Comparable)x).compareTo(y); } /** * Returns the header for base node list, or null if uninitialized */ final Node baseHead() { Index h; VarHandle.acquireFence(); return ((h = head) == null) ? null : h.node; } /** * Tries to unlink deleted node n from predecessor b (if both * exist), by first splicing in a marker if not already present. * Upon return, node n is sure to be unlinked from b, possibly * via the actions of some other thread. * * @param b if nonnull, predecessor * @param n if nonnull, node known to be deleted */ static void unlinkNode(Node b, Node n) { if (b != null && n != null) { Node f, p; for (;;) { if ((f = n.next) != null && f.key == null) { p = f.next; // already marked break; } else if (NEXT.compareAndSet(n, f, new Node(null, null, f))) { p = f; // add marker break; } } NEXT.compareAndSet(b, n, p); } } /** * Adds to element count, initializing adder if necessary * * @param c count to add */ private void addCount(long c) { LongAdder a; do {} while ((a = adder) == null && !ADDER.compareAndSet(this, null, a = new LongAdder())); a.add(c); } /** * Returns element count, initializing adder if necessary. */ final long getAdderCount() { LongAdder a; long c; do {} while ((a = adder) == null && !ADDER.compareAndSet(this, null, a = new LongAdder())); return ((c = a.sum()) <= 0L) ? 0L : c; // ignore transient negatives } /* ---------------- Traversal -------------- */ /** * Returns an index node with key strictly less than given key. * Also unlinks indexes to deleted nodes found along the way. * Callers rely on this side-effect of clearing indices to deleted * nodes. * * @param key if nonnull the key * @return a predecessor node of key, or null if uninitialized or null key */ private Node findPredecessor(Object key, Comparator cmp) { Index q; VarHandle.acquireFence(); if ((q = head) == null || key == null) return null; else { for (Index r, d;;) { while ((r = q.right) != null) { Node p; K k; if ((p = r.node) == null || (k = p.key) == null || p.val == null) // unlink index to deleted node RIGHT.compareAndSet(q, r, r.right); else if (cpr(cmp, key, k) > 0) q = r; else break; } if ((d = q.down) != null) q = d; else return q.node; } } } /** * Returns node holding key or null if no such, clearing out any * deleted nodes seen along the way. Repeatedly traverses at * base-level looking for key starting at predecessor returned * from findPredecessor, processing base-level deletions as * encountered. Restarts occur, at traversal step encountering * node n, if n's key field is null, indicating it is a marker, so * its predecessor is deleted before continuing, which we help do * by re-finding a valid predecessor. The traversal loops in * doPut, doRemove, and findNear all include the same checks. * * @param key the key * @return node holding key, or null if no such */ private Node findNode(Object key) { if (key == null) throw new NullPointerException(); // don't postpone errors Comparator cmp = comparator; Node b; outer: while ((b = findPredecessor(key, cmp)) != null) { for (;;) { Node n; K k; V v; int c; if ((n = b.next) == null) break outer; // empty else if ((k = n.key) == null) break; // b is deleted else if ((v = n.val) == null) unlinkNode(b, n); // n is deleted else if ((c = cpr(cmp, key, k)) > 0) b = n; else if (c == 0) return n; else break outer; } } return null; } /** * Gets value for key. Same idea as findNode, except skips over * deletions and markers, and returns first encountered value to * avoid possibly inconsistent rereads. * * @param key the key * @return the value, or null if absent */ private V doGet(Object key) { Index q; VarHandle.acquireFence(); if (key == null) throw new NullPointerException(); Comparator cmp = comparator; V result = null; if ((q = head) != null) { outer: for (Index r, d;;) { while ((r = q.right) != null) { Node p; K k; V v; int c; if ((p = r.node) == null || (k = p.key) == null || (v = p.val) == null) RIGHT.compareAndSet(q, r, r.right); else if ((c = cpr(cmp, key, k)) > 0) q = r; else if (c == 0) { result = v; break outer; } else break; } if ((d = q.down) != null) q = d; else { Node b, n; if ((b = q.node) != null) { while ((n = b.next) != null) { V v; int c; K k = n.key; if ((v = n.val) == null || k == null || (c = cpr(cmp, key, k)) > 0) b = n; else { if (c == 0) result = v; break; } } } break; } } } return result; } /* ---------------- Insertion -------------- */ /** * Main insertion method. Adds element if not present, or * replaces value if present and onlyIfAbsent is false. * * @param key the key * @param value the value that must be associated with key * @param onlyIfAbsent if should not insert if already present * @return the old value, or null if newly inserted */ private V doPut(K key, V value, boolean onlyIfAbsent) { if (key == null) throw new NullPointerException(); Comparator cmp = comparator; for (;;) { Index h; Node b; VarHandle.acquireFence(); int levels = 0; // number of levels descended if ((h = head) == null) { // try to initialize Node base = new Node(null, null, null); h = new Index(base, null, null); b = (HEAD.compareAndSet(this, null, h)) ? base : null; } else { for (Index q = h, r, d;;) { // count while descending while ((r = q.right) != null) { Node p; K k; if ((p = r.node) == null || (k = p.key) == null || p.val == null) RIGHT.compareAndSet(q, r, r.right); else if (cpr(cmp, key, k) > 0) q = r; else break; } if ((d = q.down) != null) { ++levels; q = d; } else { b = q.node; break; } } } if (b != null) { Node z = null; // new node, if inserted for (;;) { // find insertion point Node n, p; K k; V v; int c; if ((n = b.next) == null) { if (b.key == null) // if empty, type check key now cpr(cmp, key, key); c = -1; } else if ((k = n.key) == null) break; // can't append; restart else if ((v = n.val) == null) { unlinkNode(b, n); c = 1; } else if ((c = cpr(cmp, key, k)) > 0) b = n; else if (c == 0 && (onlyIfAbsent || VAL.compareAndSet(n, v, value))) return v; if (c < 0 && NEXT.compareAndSet(b, n, p = new Node(key, value, n))) { z = p; break; } } if (z != null) { int lr = ThreadLocalRandom.nextSecondarySeed(); if ((lr & 0x3) == 0) { // add indices with 1/4 prob int hr = ThreadLocalRandom.nextSecondarySeed(); long rnd = ((long)hr << 32) | ((long)lr & 0xffffffffL); int skips = levels; // levels to descend before add Index x = null; for (;;) { // create at most 62 indices x = new Index(z, x, null); if (rnd >= 0L || --skips < 0) break; else rnd <<= 1; } if (addIndices(h, skips, x, cmp) && skips < 0 && head == h) { // try to add new level Index hx = new Index(z, x, null); Index nh = new Index(h.node, h, hx); HEAD.compareAndSet(this, h, nh); } if (z.val == null) // deleted while adding indices findPredecessor(key, cmp); // clean } addCount(1L); return null; } } } } /** * Add indices after an insertion. Descends iteratively to the * highest level of insertion, then recursively, to chain index * nodes to lower ones. Returns null on (staleness) failure, * disabling higher-level insertions. Recursion depths are * exponentially less probable. * * @param q starting index for current level * @param skips levels to skip before inserting * @param x index for this insertion * @param cmp comparator */ static boolean addIndices(Index q, int skips, Index x, Comparator cmp) { Node z; K key; if (x != null && (z = x.node) != null && (key = z.key) != null && q != null) { // hoist checks boolean retrying = false; for (;;) { // find splice point Index r, d; int c; if ((r = q.right) != null) { Node p; K k; if ((p = r.node) == null || (k = p.key) == null || p.val == null) { RIGHT.compareAndSet(q, r, r.right); c = 0; } else if ((c = cpr(cmp, key, k)) > 0) q = r; else if (c == 0) break; // stale } else c = -1; if (c < 0) { if ((d = q.down) != null && skips > 0) { --skips; q = d; } else if (d != null && !retrying && !addIndices(d, 0, x.down, cmp)) break; else { x.right = r; if (RIGHT.compareAndSet(q, r, x)) return true; else retrying = true; // re-find splice point } } } } return false; } /* ---------------- Deletion -------------- */ /** * Main deletion method. Locates node, nulls value, appends a * deletion marker, unlinks predecessor, removes associated index * nodes, and possibly reduces head index level. * * @param key the key * @param value if non-null, the value that must be * associated with key * @return the node, or null if not found */ final V doRemove(Object key, Object value) { if (key == null) throw new NullPointerException(); Comparator cmp = comparator; V result = null; Node b; outer: while ((b = findPredecessor(key, cmp)) != null && result == null) { for (;;) { Node n; K k; V v; int c; if ((n = b.next) == null) break outer; else if ((k = n.key) == null) break; else if ((v = n.val) == null) unlinkNode(b, n); else if ((c = cpr(cmp, key, k)) > 0) b = n; else if (c < 0) break outer; else if (value != null && !value.equals(v)) break outer; else if (VAL.compareAndSet(n, v, null)) { result = v; unlinkNode(b, n); break; // loop to clean up } } } if (result != null) { tryReduceLevel(); addCount(-1L); } return result; } /** * Possibly reduce head level if it has no nodes. This method can * (rarely) make mistakes, in which case levels can disappear even * though they are about to contain index nodes. This impacts * performance, not correctness. To minimize mistakes as well as * to reduce hysteresis, the level is reduced by one only if the * topmost three levels look empty. Also, if the removed level * looks non-empty after CAS, we try to change it back quick * before anyone notices our mistake! (This trick works pretty * well because this method will practically never make mistakes * unless current thread stalls immediately before first CAS, in * which case it is very unlikely to stall again immediately * afterwards, so will recover.) * * We put up with all this rather than just let levels grow * because otherwise, even a small map that has undergone a large * number of insertions and removals will have a lot of levels, * slowing down access more than would an occasional unwanted * reduction. */ private void tryReduceLevel() { Index h, d, e; if ((h = head) != null && h.right == null && (d = h.down) != null && d.right == null && (e = d.down) != null && e.right == null && HEAD.compareAndSet(this, h, d) && h.right != null) // recheck HEAD.compareAndSet(this, d, h); // try to backout } /* ---------------- Finding and removing first element -------------- */ /** * Gets first valid node, unlinking deleted nodes if encountered. * @return first node or null if empty */ final Node findFirst() { Node b, n; if ((b = baseHead()) != null) { while ((n = b.next) != null) { if (n.val == null) unlinkNode(b, n); else return n; } } return null; } /** * Entry snapshot version of findFirst */ final AbstractMap.SimpleImmutableEntry findFirstEntry() { Node b, n; V v; if ((b = baseHead()) != null) { while ((n = b.next) != null) { if ((v = n.val) == null) unlinkNode(b, n); else return new AbstractMap.SimpleImmutableEntry(n.key, v); } } return null; } /** * Removes first entry; returns its snapshot. * @return null if empty, else snapshot of first entry */ private AbstractMap.SimpleImmutableEntry doRemoveFirstEntry() { Node b, n; V v; if ((b = baseHead()) != null) { while ((n = b.next) != null) { if ((v = n.val) == null || VAL.compareAndSet(n, v, null)) { K k = n.key; unlinkNode(b, n); if (v != null) { tryReduceLevel(); findPredecessor(k, comparator); // clean index addCount(-1L); return new AbstractMap.SimpleImmutableEntry(k, v); } } } } return null; } /* ---------------- Finding and removing last element -------------- */ /** * Specialized version of find to get last valid node. * @return last node or null if empty */ final Node findLast() { outer: for (;;) { Index q; Node b; VarHandle.acquireFence(); if ((q = head) == null) break; for (Index r, d;;) { while ((r = q.right) != null) { Node p; if ((p = r.node) == null || p.val == null) RIGHT.compareAndSet(q, r, r.right); else q = r; } if ((d = q.down) != null) q = d; else { b = q.node; break; } } if (b != null) { for (;;) { Node n; if ((n = b.next) == null) { if (b.key == null) // empty break outer; else return b; } else if (n.key == null) break; else if (n.val == null) unlinkNode(b, n); else b = n; } } } return null; } /** * Entry version of findLast * @return Entry for last node or null if empty */ final AbstractMap.SimpleImmutableEntry findLastEntry() { for (;;) { Node n; V v; if ((n = findLast()) == null) return null; if ((v = n.val) != null) return new AbstractMap.SimpleImmutableEntry(n.key, v); } } /** * Removes last entry; returns its snapshot. * Specialized variant of doRemove. * @return null if empty, else snapshot of last entry */ private Map.Entry doRemoveLastEntry() { outer: for (;;) { Index q; Node b; VarHandle.acquireFence(); if ((q = head) == null) break; for (;;) { Index d, r; Node p; while ((r = q.right) != null) { if ((p = r.node) == null || p.val == null) RIGHT.compareAndSet(q, r, r.right); else if (p.next != null) q = r; // continue only if a successor else break; } if ((d = q.down) != null) q = d; else { b = q.node; break; } } if (b != null) { for (;;) { Node n; K k; V v; if ((n = b.next) == null) { if (b.key == null) // empty break outer; else break; // retry } else if ((k = n.key) == null) break; else if ((v = n.val) == null) unlinkNode(b, n); else if (n.next != null) b = n; else if (VAL.compareAndSet(n, v, null)) { unlinkNode(b, n); tryReduceLevel(); findPredecessor(k, comparator); // clean index addCount(-1L); return new AbstractMap.SimpleImmutableEntry(k, v); } } } } return null; } /* ---------------- Relational operations -------------- */ // Control values OR'ed as arguments to findNear private static final int EQ = 1; private static final int LT = 2; private static final int GT = 0; // Actually checked as !LT /** * Utility for ceiling, floor, lower, higher methods. * @param key the key * @param rel the relation -- OR'ed combination of EQ, LT, GT * @return nearest node fitting relation, or null if no such */ final Node findNear(K key, int rel, Comparator cmp) { if (key == null) throw new NullPointerException(); Node result; outer: for (Node b;;) { if ((b = findPredecessor(key, cmp)) == null) { result = null; break; // empty } for (;;) { Node n; K k; int c; if ((n = b.next) == null) { result = ((rel & LT) != 0 && b.key != null) ? b : null; break outer; } else if ((k = n.key) == null) break; else if (n.val == null) unlinkNode(b, n); else if (((c = cpr(cmp, key, k)) == 0 && (rel & EQ) != 0) || (c < 0 && (rel & LT) == 0)) { result = n; break outer; } else if (c <= 0 && (rel & LT) != 0) { result = (b.key != null) ? b : null; break outer; } else b = n; } } return result; } /** * Variant of findNear returning SimpleImmutableEntry * @param key the key * @param rel the relation -- OR'ed combination of EQ, LT, GT * @return Entry fitting relation, or null if no such */ final AbstractMap.SimpleImmutableEntry findNearEntry(K key, int rel, Comparator cmp) { for (;;) { Node n; V v; if ((n = findNear(key, rel, cmp)) == null) return null; if ((v = n.val) != null) return new AbstractMap.SimpleImmutableEntry(n.key, v); } } /* ---------------- Constructors -------------- */ /** * Constructs a new, empty map, sorted according to the * {@linkplain Comparable natural ordering} of the keys. */ public ConcurrentSkipListMap() { this.comparator = null; } /** * Constructs a new, empty map, sorted according to the specified * comparator. * * @param comparator the comparator that will be used to order this map. * If {@code null}, the {@linkplain Comparable natural * ordering} of the keys will be used. */ public ConcurrentSkipListMap(Comparator comparator) { this.comparator = comparator; } /** * Constructs a new map containing the same mappings as the given map, * sorted according to the {@linkplain Comparable natural ordering} of * the keys. * * @param m the map whose mappings are to be placed in this map * @throws ClassCastException if the keys in {@code m} are not * {@link Comparable}, or are not mutually comparable * @throws NullPointerException if the specified map or any of its keys * or values are null */ public ConcurrentSkipListMap(Map m) { this.comparator = null; putAll(m); } /** * Constructs a new map containing the same mappings and using the * same ordering as the specified sorted map. * * @param m the sorted map whose mappings are to be placed in this * map, and whose comparator is to be used to sort this map * @throws NullPointerException if the specified sorted map or any of * its keys or values are null */ public ConcurrentSkipListMap(SortedMap m) { this.comparator = m.comparator(); buildFromSorted(m); // initializes transients } /** * Returns a shallow copy of this {@code ConcurrentSkipListMap} * instance. (The keys and values themselves are not cloned.) * * @return a shallow copy of this map */ public ConcurrentSkipListMap clone() { try { @SuppressWarnings("unchecked") ConcurrentSkipListMap clone = (ConcurrentSkipListMap) super.clone(); clone.keySet = null; clone.entrySet = null; clone.values = null; clone.descendingMap = null; clone.buildFromSorted(this); return clone; } catch (CloneNotSupportedException e) { throw new InternalError(); } } /** * Streamlined bulk insertion to initialize from elements of * given sorted map. Call only from constructor or clone * method. */ private void buildFromSorted(SortedMap map) { if (map == null) throw new NullPointerException(); Iterator> it = map.entrySet().iterator(); /* * Add equally spaced indices at log intervals, using the bits * of count during insertion. The maximum possible resulting * level is less than the number of bits in a long (64). The * preds array tracks the current rightmost node at each * level. */ @SuppressWarnings("unchecked") Index[] preds = (Index[])new Index[64]; Node bp = new Node(null, null, null); Index h = preds[0] = new Index(bp, null, null); long count = 0; while (it.hasNext()) { Map.Entry e = it.next(); K k = e.getKey(); V v = e.getValue(); if (k == null || v == null) throw new NullPointerException(); Node z = new Node(k, v, null); bp = bp.next = z; if ((++count & 3L) == 0L) { long m = count >>> 2; int i = 0; Index idx = null, q; do { idx = new Index(z, idx, null); if ((q = preds[i]) == null) preds[i] = h = new Index(h.node, h, idx); else preds[i] = q.right = idx; } while (++i < preds.length && ((m >>>= 1) & 1L) != 0L); } } if (count != 0L) { VarHandle.releaseFence(); // emulate volatile stores addCount(count); head = h; VarHandle.fullFence(); } } /* ---------------- Serialization -------------- */ /** * Saves this map to a stream (that is, serializes it). * * @param s the stream * @throws java.io.IOException if an I/O error occurs * @serialData The key (Object) and value (Object) for each * key-value mapping represented by the map, followed by * {@code null}. The key-value mappings are emitted in key-order * (as determined by the Comparator, or by the keys' natural * ordering if no Comparator). */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out the Comparator and any hidden stuff s.defaultWriteObject(); // Write out keys and values (alternating) Node b, n; V v; if ((b = baseHead()) != null) { while ((n = b.next) != null) { if ((v = n.val) != null) { s.writeObject(n.key); s.writeObject(v); } b = n; } } s.writeObject(null); } /** * Reconstitutes this map from a stream (that is, deserializes it). * @param s the stream * @throws ClassNotFoundException if the class of a serialized object * could not be found * @throws java.io.IOException if an I/O error occurs */ @SuppressWarnings("unchecked") private void readObject(final java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in the Comparator and any hidden stuff s.defaultReadObject(); // Same idea as buildFromSorted @SuppressWarnings("unchecked") Index[] preds = (Index[])new Index[64]; Node bp = new Node(null, null, null); Index h = preds[0] = new Index(bp, null, null); Comparator cmp = comparator; K prevKey = null; long count = 0; for (;;) { K k = (K)s.readObject(); if (k == null) break; V v = (V)s.readObject(); if (v == null) throw new NullPointerException(); if (prevKey != null && cpr(cmp, prevKey, k) > 0) throw new IllegalStateException("out of order"); prevKey = k; Node z = new Node(k, v, null); bp = bp.next = z; if ((++count & 3L) == 0L) { long m = count >>> 2; int i = 0; Index idx = null, q; do { idx = new Index(z, idx, null); if ((q = preds[i]) == null) preds[i] = h = new Index(h.node, h, idx); else preds[i] = q.right = idx; } while (++i < preds.length && ((m >>>= 1) & 1L) != 0L); } } if (count != 0L) { VarHandle.releaseFence(); addCount(count); head = h; VarHandle.fullFence(); } } /* ------ Map API methods ------ */ /** * Returns {@code true} if this map contains a mapping for the specified * key. * * @param key key whose presence in this map is to be tested * @return {@code true} if this map contains a mapping for the specified key * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key is null */ public boolean containsKey(Object key) { return doGet(key) != null; } /** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * *

More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code key} compares * equal to {@code k} according to the map's ordering, then this * method returns {@code v}; otherwise it returns {@code null}. * (There can be at most one such mapping.) * * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key is null */ public V get(Object key) { return doGet(key); } /** * Returns the value to which the specified key is mapped, * or the given defaultValue if this map contains no mapping for the key. * * @param key the key * @param defaultValue the value to return if this map contains * no mapping for the given key * @return the mapping for the key, if present; else the defaultValue * @throws NullPointerException if the specified key is null * @since 1.8 */ public V getOrDefault(Object key, V defaultValue) { V v; return (v = doGet(key)) == null ? defaultValue : v; } /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with the specified key, or * {@code null} if there was no mapping for the key * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key or value is null */ public V put(K key, V value) { if (value == null) throw new NullPointerException(); return doPut(key, value, false); } /** * Removes the mapping for the specified key from this map if present. * * @param key key for which mapping should be removed * @return the previous value associated with the specified key, or * {@code null} if there was no mapping for the key * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key is null */ public V remove(Object key) { return doRemove(key, null); } /** * Returns {@code true} if this map maps one or more keys to the * specified value. This operation requires time linear in the * map size. Additionally, it is possible for the map to change * during execution of this method, in which case the returned * result may be inaccurate. * * @param value value whose presence in this map is to be tested * @return {@code true} if a mapping to {@code value} exists; * {@code false} otherwise * @throws NullPointerException if the specified value is null */ public boolean containsValue(Object value) { if (value == null) throw new NullPointerException(); Node b, n; V v; if ((b = baseHead()) != null) { while ((n = b.next) != null) { if ((v = n.val) != null && value.equals(v)) return true; else b = n; } } return false; } /** * {@inheritDoc} */ public int size() { long c; return ((baseHead() == null) ? 0 : ((c = getAdderCount()) >= Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) c); } /** * {@inheritDoc} */ public boolean isEmpty() { return findFirst() == null; } /** * Removes all of the mappings from this map. */ public void clear() { Index h, r, d; Node b; VarHandle.acquireFence(); while ((h = head) != null) { if ((r = h.right) != null) // remove indices RIGHT.compareAndSet(h, r, null); else if ((d = h.down) != null) // remove levels HEAD.compareAndSet(this, h, d); else { long count = 0L; if ((b = h.node) != null) { // remove nodes Node n; V v; while ((n = b.next) != null) { if ((v = n.val) != null && VAL.compareAndSet(n, v, null)) { --count; v = null; } if (v == null) unlinkNode(b, n); } } if (count != 0L) addCount(count); else break; } } } /** * If the specified key is not already associated with a value, * attempts to compute its value using the given mapping function * and enters it into this map unless {@code null}. The function * is NOT guaranteed to be applied once atomically only * if the value is not present. * * @param key key with which the specified value is to be associated * @param mappingFunction the function to compute a value * @return the current (existing or computed) value associated with * the specified key, or null if the computed value is null * @throws NullPointerException if the specified key is null * or the mappingFunction is null * @since 1.8 */ public V computeIfAbsent(K key, Function mappingFunction) { if (key == null || mappingFunction == null) throw new NullPointerException(); V v, p, r; if ((v = doGet(key)) == null && (r = mappingFunction.apply(key)) != null) v = (p = doPut(key, r, true)) == null ? r : p; return v; } /** * If the value for the specified key is present, attempts to * compute a new mapping given the key and its current mapped * value. The function is NOT guaranteed to be applied * once atomically. * * @param key key with which a value may be associated * @param remappingFunction the function to compute a value * @return the new value associated with the specified key, or null if none * @throws NullPointerException if the specified key is null * or the remappingFunction is null * @since 1.8 */ public V computeIfPresent(K key, BiFunction remappingFunction) { if (key == null || remappingFunction == null) throw new NullPointerException(); Node n; V v; while ((n = findNode(key)) != null) { if ((v = n.val) != null) { V r = remappingFunction.apply(key, v); if (r != null) { if (VAL.compareAndSet(n, v, r)) return r; } else if (doRemove(key, v) != null) break; } } return null; } /** * Attempts to compute a mapping for the specified key and its * current mapped value (or {@code null} if there is no current * mapping). The function is NOT guaranteed to be applied * once atomically. * * @param key key with which the specified value is to be associated * @param remappingFunction the function to compute a value * @return the new value associated with the specified key, or null if none * @throws NullPointerException if the specified key is null * or the remappingFunction is null * @since 1.8 */ public V compute(K key, BiFunction remappingFunction) { if (key == null || remappingFunction == null) throw new NullPointerException(); for (;;) { Node n; V v; V r; if ((n = findNode(key)) == null) { if ((r = remappingFunction.apply(key, null)) == null) break; if (doPut(key, r, true) == null) return r; } else if ((v = n.val) != null) { if ((r = remappingFunction.apply(key, v)) != null) { if (VAL.compareAndSet(n, v, r)) return r; } else if (doRemove(key, v) != null) break; } } return null; } /** * If the specified key is not already associated with a value, * associates it with the given value. Otherwise, replaces the * value with the results of the given remapping function, or * removes if {@code null}. The function is NOT * guaranteed to be applied once atomically. * * @param key key with which the specified value is to be associated * @param value the value to use if absent * @param remappingFunction the function to recompute a value if present * @return the new value associated with the specified key, or null if none * @throws NullPointerException if the specified key or value is null * or the remappingFunction is null * @since 1.8 */ public V merge(K key, V value, BiFunction remappingFunction) { if (key == null || value == null || remappingFunction == null) throw new NullPointerException(); for (;;) { Node n; V v; V r; if ((n = findNode(key)) == null) { if (doPut(key, value, true) == null) return value; } else if ((v = n.val) != null) { if ((r = remappingFunction.apply(v, value)) != null) { if (VAL.compareAndSet(n, v, r)) return r; } else if (doRemove(key, v) != null) return null; } } } /* ---------------- View methods -------------- */ /* * Note: Lazy initialization works for views because view classes * are stateless/immutable so it doesn't matter wrt correctness if * more than one is created (which will only rarely happen). Even * so, the following idiom conservatively ensures that the method * returns the one it created if it does so, not one created by * another racing thread. */ /** * Returns a {@link NavigableSet} view of the keys contained in this map. * *

The set's iterator returns the keys in ascending order. * The set's spliterator additionally reports {@link Spliterator#CONCURRENT}, * {@link Spliterator#NONNULL}, {@link Spliterator#SORTED} and * {@link Spliterator#ORDERED}, with an encounter order that is ascending * key order. * *

The {@linkplain Spliterator#getComparator() spliterator's comparator} * is {@code null} if the {@linkplain #comparator() map's comparator} * is {@code null}. * Otherwise, the spliterator's comparator is the same as or imposes the * same total ordering as the map's comparator. * *

The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. The set supports element * removal, which removes the corresponding mapping from the map, * via the {@code Iterator.remove}, {@code Set.remove}, * {@code removeAll}, {@code retainAll}, and {@code clear} * operations. It does not support the {@code add} or {@code addAll} * operations. * *

The view's iterators and spliterators are * weakly consistent. * *

This method is equivalent to method {@code navigableKeySet}. * * @return a navigable set view of the keys in this map */ public NavigableSet keySet() { KeySet ks; if ((ks = keySet) != null) return ks; return keySet = new KeySet<>(this); } public NavigableSet navigableKeySet() { KeySet ks; if ((ks = keySet) != null) return ks; return keySet = new KeySet<>(this); } /** * Returns a {@link Collection} view of the values contained in this map. *

The collection's iterator returns the values in ascending order * of the corresponding keys. The collections's spliterator additionally * reports {@link Spliterator#CONCURRENT}, {@link Spliterator#NONNULL} and * {@link Spliterator#ORDERED}, with an encounter order that is ascending * order of the corresponding keys. * *

The collection is backed by the map, so changes to the map are * reflected in the collection, and vice-versa. The collection * supports element removal, which removes the corresponding * mapping from the map, via the {@code Iterator.remove}, * {@code Collection.remove}, {@code removeAll}, * {@code retainAll} and {@code clear} operations. It does not * support the {@code add} or {@code addAll} operations. * *

The view's iterators and spliterators are * weakly consistent. */ public Collection values() { Values vs; if ((vs = values) != null) return vs; return values = new Values<>(this); } /** * Returns a {@link Set} view of the mappings contained in this map. * *

The set's iterator returns the entries in ascending key order. The * set's spliterator additionally reports {@link Spliterator#CONCURRENT}, * {@link Spliterator#NONNULL}, {@link Spliterator#SORTED} and * {@link Spliterator#ORDERED}, with an encounter order that is ascending * key order. * *

The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. The set supports element * removal, which removes the corresponding mapping from the map, * via the {@code Iterator.remove}, {@code Set.remove}, * {@code removeAll}, {@code retainAll} and {@code clear} * operations. It does not support the {@code add} or * {@code addAll} operations. * *

The view's iterators and spliterators are * weakly consistent. * *

The {@code Map.Entry} elements traversed by the {@code iterator} * or {@code spliterator} do not support the {@code setValue} * operation. * * @return a set view of the mappings contained in this map, * sorted in ascending key order */ public Set> entrySet() { EntrySet es; if ((es = entrySet) != null) return es; return entrySet = new EntrySet(this); } public ConcurrentNavigableMap descendingMap() { ConcurrentNavigableMap dm; if ((dm = descendingMap) != null) return dm; return descendingMap = new SubMap(this, null, false, null, false, true); } public NavigableSet descendingKeySet() { return descendingMap().navigableKeySet(); } /* ---------------- AbstractMap Overrides -------------- */ /** * Compares the specified object with this map for equality. * Returns {@code true} if the given object is also a map and the * two maps represent the same mappings. More formally, two maps * {@code m1} and {@code m2} represent the same mappings if * {@code m1.entrySet().equals(m2.entrySet())}. This * operation may return misleading results if either map is * concurrently modified during execution of this method. * * @param o object to be compared for equality with this map * @return {@code true} if the specified object is equal to this map */ public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Map)) return false; Map m = (Map) o; try { Comparator cmp = comparator; @SuppressWarnings("unchecked") Iterator> it = (Iterator>)m.entrySet().iterator(); if (m instanceof SortedMap && ((SortedMap)m).comparator() == cmp) { Node b, n; if ((b = baseHead()) != null) { while ((n = b.next) != null) { K k; V v; if ((v = n.val) != null && (k = n.key) != null) { if (!it.hasNext()) return false; Map.Entry e = it.next(); Object mk = e.getKey(); Object mv = e.getValue(); if (mk == null || mv == null) return false; try { if (cpr(cmp, k, mk) != 0) return false; } catch (ClassCastException cce) { return false; } if (!mv.equals(v)) return false; } b = n; } } return !it.hasNext(); } else { while (it.hasNext()) { V v; Map.Entry e = it.next(); Object mk = e.getKey(); Object mv = e.getValue(); if (mk == null || mv == null || (v = get(mk)) == null || !v.equals(mv)) return false; } Node b, n; if ((b = baseHead()) != null) { K k; V v; Object mv; while ((n = b.next) != null) { if ((v = n.val) != null && (k = n.key) != null && ((mv = m.get(k)) == null || !mv.equals(v))) return false; b = n; } } return true; } } catch (ClassCastException unused) { return false; } catch (NullPointerException unused) { return false; } } /* ------ ConcurrentMap API methods ------ */ /** * {@inheritDoc} * * @return the previous value associated with the specified key, * or {@code null} if there was no mapping for the key * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key or value is null */ public V putIfAbsent(K key, V value) { if (value == null) throw new NullPointerException(); return doPut(key, value, true); } /** * {@inheritDoc} * * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key is null */ public boolean remove(Object key, Object value) { if (key == null) throw new NullPointerException(); return value != null && doRemove(key, value) != null; } /** * {@inheritDoc} * * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if any of the arguments are null */ public boolean replace(K key, V oldValue, V newValue) { if (key == null || oldValue == null || newValue == null) throw new NullPointerException(); for (;;) { Node n; V v; if ((n = findNode(key)) == null) return false; if ((v = n.val) != null) { if (!oldValue.equals(v)) return false; if (VAL.compareAndSet(n, v, newValue)) return true; } } } /** * {@inheritDoc} * * @return the previous value associated with the specified key, * or {@code null} if there was no mapping for the key * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key or value is null */ public V replace(K key, V value) { if (key == null || value == null) throw new NullPointerException(); for (;;) { Node n; V v; if ((n = findNode(key)) == null) return null; if ((v = n.val) != null && VAL.compareAndSet(n, v, value)) return v; } } /* ------ SortedMap API methods ------ */ public Comparator comparator() { return comparator; } /** * @throws NoSuchElementException {@inheritDoc} */ public K firstKey() { Node n = findFirst(); if (n == null) throw new NoSuchElementException(); return n.key; } /** * @throws NoSuchElementException {@inheritDoc} */ public K lastKey() { Node n = findLast(); if (n == null) throw new NoSuchElementException(); return n.key; } /** * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if {@code fromKey} or {@code toKey} is null * @throws IllegalArgumentException {@inheritDoc} */ public ConcurrentNavigableMap subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { if (fromKey == null || toKey == null) throw new NullPointerException(); return new SubMap (this, fromKey, fromInclusive, toKey, toInclusive, false); } /** * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if {@code toKey} is null * @throws IllegalArgumentException {@inheritDoc} */ public ConcurrentNavigableMap headMap(K toKey, boolean inclusive) { if (toKey == null) throw new NullPointerException(); return new SubMap (this, null, false, toKey, inclusive, false); } /** * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if {@code fromKey} is null * @throws IllegalArgumentException {@inheritDoc} */ public ConcurrentNavigableMap tailMap(K fromKey, boolean inclusive) { if (fromKey == null) throw new NullPointerException(); return new SubMap (this, fromKey, inclusive, null, false, false); } /** * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if {@code fromKey} or {@code toKey} is null * @throws IllegalArgumentException {@inheritDoc} */ public ConcurrentNavigableMap subMap(K fromKey, K toKey) { return subMap(fromKey, true, toKey, false); } /** * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if {@code toKey} is null * @throws IllegalArgumentException {@inheritDoc} */ public ConcurrentNavigableMap headMap(K toKey) { return headMap(toKey, false); } /** * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if {@code fromKey} is null * @throws IllegalArgumentException {@inheritDoc} */ public ConcurrentNavigableMap tailMap(K fromKey) { return tailMap(fromKey, true); } /* ---------------- Relational operations -------------- */ /** * Returns a key-value mapping associated with the greatest key * strictly less than the given key, or {@code null} if there is * no such key. The returned entry does not support the * {@code Entry.setValue} method. * * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if the specified key is null */ public Map.Entry lowerEntry(K key) { return findNearEntry(key, LT, comparator); } /** * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if the specified key is null */ public K lowerKey(K key) { Node n = findNear(key, LT, comparator); return (n == null) ? null : n.key; } /** * Returns a key-value mapping associated with the greatest key * less than or equal to the given key, or {@code null} if there * is no such key. The returned entry does not support * the {@code Entry.setValue} method. * * @param key the key * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if the specified key is null */ public Map.Entry floorEntry(K key) { return findNearEntry(key, LT|EQ, comparator); } /** * @param key the key * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if the specified key is null */ public K floorKey(K key) { Node n = findNear(key, LT|EQ, comparator); return (n == null) ? null : n.key; } /** * Returns a key-value mapping associated with the least key * greater than or equal to the given key, or {@code null} if * there is no such entry. The returned entry does not * support the {@code Entry.setValue} method. * * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if the specified key is null */ public Map.Entry ceilingEntry(K key) { return findNearEntry(key, GT|EQ, comparator); } /** * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if the specified key is null */ public K ceilingKey(K key) { Node n = findNear(key, GT|EQ, comparator); return (n == null) ? null : n.key; } /** * Returns a key-value mapping associated with the least key * strictly greater than the given key, or {@code null} if there * is no such key. The returned entry does not support * the {@code Entry.setValue} method. * * @param key the key * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if the specified key is null */ public Map.Entry higherEntry(K key) { return findNearEntry(key, GT, comparator); } /** * @param key the key * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if the specified key is null */ public K higherKey(K key) { Node n = findNear(key, GT, comparator); return (n == null) ? null : n.key; } /** * Returns a key-value mapping associated with the least * key in this map, or {@code null} if the map is empty. * The returned entry does not support * the {@code Entry.setValue} method. */ public Map.Entry firstEntry() { return findFirstEntry(); } /** * Returns a key-value mapping associated with the greatest * key in this map, or {@code null} if the map is empty. * The returned entry does not support * the {@code Entry.setValue} method. */ public Map.Entry lastEntry() { return findLastEntry(); } /** * Removes and returns a key-value mapping associated with * the least key in this map, or {@code null} if the map is empty. * The returned entry does not support * the {@code Entry.setValue} method. */ public Map.Entry pollFirstEntry() { return doRemoveFirstEntry(); } /** * Removes and returns a key-value mapping associated with * the greatest key in this map, or {@code null} if the map is empty. * The returned entry does not support * the {@code Entry.setValue} method. */ public Map.Entry pollLastEntry() { return doRemoveLastEntry(); } /* ---------------- Iterators -------------- */ /** * Base of iterator classes */ abstract class Iter implements Iterator { /** the last node returned by next() */ Node lastReturned; /** the next node to return from next(); */ Node next; /** Cache of next value field to maintain weak consistency */ V nextValue; /** Initializes ascending iterator for entire range. */ Iter() { advance(baseHead()); } public final boolean hasNext() { return next != null; } /** Advances next to higher entry. */ final void advance(Node b) { Node n = null; V v = null; if ((lastReturned = b) != null) { while ((n = b.next) != null && (v = n.val) == null) b = n; } nextValue = v; next = n; } public final void remove() { Node n; K k; if ((n = lastReturned) == null || (k = n.key) == null) throw new IllegalStateException(); // It would not be worth all of the overhead to directly // unlink from here. Using remove is fast enough. ConcurrentSkipListMap.this.remove(k); lastReturned = null; } } final class ValueIterator extends Iter { public V next() { V v; if ((v = nextValue) == null) throw new NoSuchElementException(); advance(next); return v; } } final class KeyIterator extends Iter { public K next() { Node n; if ((n = next) == null) throw new NoSuchElementException(); K k = n.key; advance(n); return k; } } final class EntryIterator extends Iter> { public Map.Entry next() { Node n; if ((n = next) == null) throw new NoSuchElementException(); K k = n.key; V v = nextValue; advance(n); return new AbstractMap.SimpleImmutableEntry(k, v); } } /* ---------------- View Classes -------------- */ /* * View classes are static, delegating to a ConcurrentNavigableMap * to allow use by SubMaps, which outweighs the ugliness of * needing type-tests for Iterator methods. */ static final List toList(Collection c) { // Using size() here would be a pessimization. ArrayList list = new ArrayList(); for (E e : c) list.add(e); return list; } static final class KeySet extends AbstractSet implements NavigableSet { final ConcurrentNavigableMap m; KeySet(ConcurrentNavigableMap map) { m = map; } public int size() { return m.size(); } public boolean isEmpty() { return m.isEmpty(); } public boolean contains(Object o) { return m.containsKey(o); } public boolean remove(Object o) { return m.remove(o) != null; } public void clear() { m.clear(); } public K lower(K e) { return m.lowerKey(e); } public K floor(K e) { return m.floorKey(e); } public K ceiling(K e) { return m.ceilingKey(e); } public K higher(K e) { return m.higherKey(e); } public Comparator comparator() { return m.comparator(); } public K first() { return m.firstKey(); } public K last() { return m.lastKey(); } public K pollFirst() { Map.Entry e = m.pollFirstEntry(); return (e == null) ? null : e.getKey(); } public K pollLast() { Map.Entry e = m.pollLastEntry(); return (e == null) ? null : e.getKey(); } public Iterator iterator() { return (m instanceof ConcurrentSkipListMap) ? ((ConcurrentSkipListMap)m).new KeyIterator() : ((SubMap)m).new SubMapKeyIterator(); } public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Set)) return false; Collection c = (Collection) o; try { return containsAll(c) && c.containsAll(this); } catch (ClassCastException | NullPointerException unused) { return false; } } public Object[] toArray() { return toList(this).toArray(); } public T[] toArray(T[] a) { return toList(this).toArray(a); } public Iterator descendingIterator() { return descendingSet().iterator(); } public NavigableSet subSet(K fromElement, boolean fromInclusive, K toElement, boolean toInclusive) { return new KeySet<>(m.subMap(fromElement, fromInclusive, toElement, toInclusive)); } public NavigableSet headSet(K toElement, boolean inclusive) { return new KeySet<>(m.headMap(toElement, inclusive)); } public NavigableSet tailSet(K fromElement, boolean inclusive) { return new KeySet<>(m.tailMap(fromElement, inclusive)); } public NavigableSet subSet(K fromElement, K toElement) { return subSet(fromElement, true, toElement, false); } public NavigableSet headSet(K toElement) { return headSet(toElement, false); } public NavigableSet tailSet(K fromElement) { return tailSet(fromElement, true); } public NavigableSet descendingSet() { return new KeySet<>(m.descendingMap()); } public Spliterator spliterator() { return (m instanceof ConcurrentSkipListMap) ? ((ConcurrentSkipListMap)m).keySpliterator() : ((SubMap)m).new SubMapKeyIterator(); } } static final class Values extends AbstractCollection { final ConcurrentNavigableMap m; Values(ConcurrentNavigableMap map) { m = map; } public Iterator iterator() { return (m instanceof ConcurrentSkipListMap) ? ((ConcurrentSkipListMap)m).new ValueIterator() : ((SubMap)m).new SubMapValueIterator(); } public int size() { return m.size(); } public boolean isEmpty() { return m.isEmpty(); } public boolean contains(Object o) { return m.containsValue(o); } public void clear() { m.clear(); } public Object[] toArray() { return toList(this).toArray(); } public T[] toArray(T[] a) { return toList(this).toArray(a); } public Spliterator spliterator() { return (m instanceof ConcurrentSkipListMap) ? ((ConcurrentSkipListMap)m).valueSpliterator() : ((SubMap)m).new SubMapValueIterator(); } public boolean removeIf(Predicate filter) { if (filter == null) throw new NullPointerException(); if (m instanceof ConcurrentSkipListMap) return ((ConcurrentSkipListMap)m).removeValueIf(filter); // else use iterator Iterator> it = ((SubMap)m).new SubMapEntryIterator(); boolean removed = false; while (it.hasNext()) { Map.Entry e = it.next(); V v = e.getValue(); if (filter.test(v) && m.remove(e.getKey(), v)) removed = true; } return removed; } } static final class EntrySet extends AbstractSet> { final ConcurrentNavigableMap m; EntrySet(ConcurrentNavigableMap map) { m = map; } public Iterator> iterator() { return (m instanceof ConcurrentSkipListMap) ? ((ConcurrentSkipListMap)m).new EntryIterator() : ((SubMap)m).new SubMapEntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; V v = m.get(e.getKey()); return v != null && v.equals(e.getValue()); } public boolean remove(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; return m.remove(e.getKey(), e.getValue()); } public boolean isEmpty() { return m.isEmpty(); } public int size() { return m.size(); } public void clear() { m.clear(); } public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Set)) return false; Collection c = (Collection) o; try { return containsAll(c) && c.containsAll(this); } catch (ClassCastException | NullPointerException unused) { return false; } } public Object[] toArray() { return toList(this).toArray(); } public T[] toArray(T[] a) { return toList(this).toArray(a); } public Spliterator> spliterator() { return (m instanceof ConcurrentSkipListMap) ? ((ConcurrentSkipListMap)m).entrySpliterator() : ((SubMap)m).new SubMapEntryIterator(); } public boolean removeIf(Predicate> filter) { if (filter == null) throw new NullPointerException(); if (m instanceof ConcurrentSkipListMap) return ((ConcurrentSkipListMap)m).removeEntryIf(filter); // else use iterator Iterator> it = ((SubMap)m).new SubMapEntryIterator(); boolean removed = false; while (it.hasNext()) { Map.Entry e = it.next(); if (filter.test(e) && m.remove(e.getKey(), e.getValue())) removed = true; } return removed; } } /** * Submaps returned by {@link ConcurrentSkipListMap} submap operations * represent a subrange of mappings of their underlying maps. * Instances of this class support all methods of their underlying * maps, differing in that mappings outside their range are ignored, * and attempts to add mappings outside their ranges result in {@link * IllegalArgumentException}. Instances of this class are constructed * only using the {@code subMap}, {@code headMap}, and {@code tailMap} * methods of their underlying maps. * * @serial include */ static final class SubMap extends AbstractMap implements ConcurrentNavigableMap, Serializable { private static final long serialVersionUID = -7647078645895051609L; /** Underlying map */ final ConcurrentSkipListMap m; /** lower bound key, or null if from start */ private final K lo; /** upper bound key, or null if to end */ private final K hi; /** inclusion flag for lo */ private final boolean loInclusive; /** inclusion flag for hi */ private final boolean hiInclusive; /** direction */ final boolean isDescending; // Lazily initialized view holders private transient KeySet keySetView; private transient Values valuesView; private transient EntrySet entrySetView; /** * Creates a new submap, initializing all fields. */ SubMap(ConcurrentSkipListMap map, K fromKey, boolean fromInclusive, K toKey, boolean toInclusive, boolean isDescending) { Comparator cmp = map.comparator; if (fromKey != null && toKey != null && cpr(cmp, fromKey, toKey) > 0) throw new IllegalArgumentException("inconsistent range"); this.m = map; this.lo = fromKey; this.hi = toKey; this.loInclusive = fromInclusive; this.hiInclusive = toInclusive; this.isDescending = isDescending; } /* ---------------- Utilities -------------- */ boolean tooLow(Object key, Comparator cmp) { int c; return (lo != null && ((c = cpr(cmp, key, lo)) < 0 || (c == 0 && !loInclusive))); } boolean tooHigh(Object key, Comparator cmp) { int c; return (hi != null && ((c = cpr(cmp, key, hi)) > 0 || (c == 0 && !hiInclusive))); } boolean inBounds(Object key, Comparator cmp) { return !tooLow(key, cmp) && !tooHigh(key, cmp); } void checkKeyBounds(K key, Comparator cmp) { if (key == null) throw new NullPointerException(); if (!inBounds(key, cmp)) throw new IllegalArgumentException("key out of range"); } /** * Returns true if node key is less than upper bound of range. */ boolean isBeforeEnd(ConcurrentSkipListMap.Node n, Comparator cmp) { if (n == null) return false; if (hi == null) return true; K k = n.key; if (k == null) // pass by markers and headers return true; int c = cpr(cmp, k, hi); return c < 0 || (c == 0 && hiInclusive); } /** * Returns lowest node. This node might not be in range, so * most usages need to check bounds. */ ConcurrentSkipListMap.Node loNode(Comparator cmp) { if (lo == null) return m.findFirst(); else if (loInclusive) return m.findNear(lo, GT|EQ, cmp); else return m.findNear(lo, GT, cmp); } /** * Returns highest node. This node might not be in range, so * most usages need to check bounds. */ ConcurrentSkipListMap.Node hiNode(Comparator cmp) { if (hi == null) return m.findLast(); else if (hiInclusive) return m.findNear(hi, LT|EQ, cmp); else return m.findNear(hi, LT, cmp); } /** * Returns lowest absolute key (ignoring directionality). */ K lowestKey() { Comparator cmp = m.comparator; ConcurrentSkipListMap.Node n = loNode(cmp); if (isBeforeEnd(n, cmp)) return n.key; else throw new NoSuchElementException(); } /** * Returns highest absolute key (ignoring directionality). */ K highestKey() { Comparator cmp = m.comparator; ConcurrentSkipListMap.Node n = hiNode(cmp); if (n != null) { K last = n.key; if (inBounds(last, cmp)) return last; } throw new NoSuchElementException(); } Map.Entry lowestEntry() { Comparator cmp = m.comparator; for (;;) { ConcurrentSkipListMap.Node n; V v; if ((n = loNode(cmp)) == null || !isBeforeEnd(n, cmp)) return null; else if ((v = n.val) != null) return new AbstractMap.SimpleImmutableEntry(n.key, v); } } Map.Entry highestEntry() { Comparator cmp = m.comparator; for (;;) { ConcurrentSkipListMap.Node n; V v; if ((n = hiNode(cmp)) == null || !inBounds(n.key, cmp)) return null; else if ((v = n.val) != null) return new AbstractMap.SimpleImmutableEntry(n.key, v); } } Map.Entry removeLowest() { Comparator cmp = m.comparator; for (;;) { ConcurrentSkipListMap.Node n; K k; V v; if ((n = loNode(cmp)) == null) return null; else if (!inBounds((k = n.key), cmp)) return null; else if ((v = m.doRemove(k, null)) != null) return new AbstractMap.SimpleImmutableEntry(k, v); } } Map.Entry removeHighest() { Comparator cmp = m.comparator; for (;;) { ConcurrentSkipListMap.Node n; K k; V v; if ((n = hiNode(cmp)) == null) return null; else if (!inBounds((k = n.key), cmp)) return null; else if ((v = m.doRemove(k, null)) != null) return new AbstractMap.SimpleImmutableEntry(k, v); } } /** * Submap version of ConcurrentSkipListMap.findNearEntry. */ Map.Entry getNearEntry(K key, int rel) { Comparator cmp = m.comparator; if (isDescending) { // adjust relation for direction if ((rel & LT) == 0) rel |= LT; else rel &= ~LT; } if (tooLow(key, cmp)) return ((rel & LT) != 0) ? null : lowestEntry(); if (tooHigh(key, cmp)) return ((rel & LT) != 0) ? highestEntry() : null; AbstractMap.SimpleImmutableEntry e = m.findNearEntry(key, rel, cmp); if (e == null || !inBounds(e.getKey(), cmp)) return null; else return e; } // Almost the same as getNearEntry, except for keys K getNearKey(K key, int rel) { Comparator cmp = m.comparator; if (isDescending) { // adjust relation for direction if ((rel & LT) == 0) rel |= LT; else rel &= ~LT; } if (tooLow(key, cmp)) { if ((rel & LT) == 0) { ConcurrentSkipListMap.Node n = loNode(cmp); if (isBeforeEnd(n, cmp)) return n.key; } return null; } if (tooHigh(key, cmp)) { if ((rel & LT) != 0) { ConcurrentSkipListMap.Node n = hiNode(cmp); if (n != null) { K last = n.key; if (inBounds(last, cmp)) return last; } } return null; } for (;;) { Node n = m.findNear(key, rel, cmp); if (n == null || !inBounds(n.key, cmp)) return null; if (n.val != null) return n.key; } } /* ---------------- Map API methods -------------- */ public boolean containsKey(Object key) { if (key == null) throw new NullPointerException(); return inBounds(key, m.comparator) && m.containsKey(key); } public V get(Object key) { if (key == null) throw new NullPointerException(); return (!inBounds(key, m.comparator)) ? null : m.get(key); } public V put(K key, V value) { checkKeyBounds(key, m.comparator); return m.put(key, value); } public V remove(Object key) { return (!inBounds(key, m.comparator)) ? null : m.remove(key); } public int size() { Comparator cmp = m.comparator; long count = 0; for (ConcurrentSkipListMap.Node n = loNode(cmp); isBeforeEnd(n, cmp); n = n.next) { if (n.val != null) ++count; } return count >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)count; } public boolean isEmpty() { Comparator cmp = m.comparator; return !isBeforeEnd(loNode(cmp), cmp); } public boolean containsValue(Object value) { if (value == null) throw new NullPointerException(); Comparator cmp = m.comparator; for (ConcurrentSkipListMap.Node n = loNode(cmp); isBeforeEnd(n, cmp); n = n.next) { V v = n.val; if (v != null && value.equals(v)) return true; } return false; } public void clear() { Comparator cmp = m.comparator; for (ConcurrentSkipListMap.Node n = loNode(cmp); isBeforeEnd(n, cmp); n = n.next) { if (n.val != null) m.remove(n.key); } } /* ---------------- ConcurrentMap API methods -------------- */ public V putIfAbsent(K key, V value) { checkKeyBounds(key, m.comparator); return m.putIfAbsent(key, value); } public boolean remove(Object key, Object value) { return inBounds(key, m.comparator) && m.remove(key, value); } public boolean replace(K key, V oldValue, V newValue) { checkKeyBounds(key, m.comparator); return m.replace(key, oldValue, newValue); } public V replace(K key, V value) { checkKeyBounds(key, m.comparator); return m.replace(key, value); } /* ---------------- SortedMap API methods -------------- */ public Comparator comparator() { Comparator cmp = m.comparator(); if (isDescending) return Collections.reverseOrder(cmp); else return cmp; } /** * Utility to create submaps, where given bounds override * unbounded(null) ones and/or are checked against bounded ones. */ SubMap newSubMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { Comparator cmp = m.comparator; if (isDescending) { // flip senses K tk = fromKey; fromKey = toKey; toKey = tk; boolean ti = fromInclusive; fromInclusive = toInclusive; toInclusive = ti; } if (lo != null) { if (fromKey == null) { fromKey = lo; fromInclusive = loInclusive; } else { int c = cpr(cmp, fromKey, lo); if (c < 0 || (c == 0 && !loInclusive && fromInclusive)) throw new IllegalArgumentException("key out of range"); } } if (hi != null) { if (toKey == null) { toKey = hi; toInclusive = hiInclusive; } else { int c = cpr(cmp, toKey, hi); if (c > 0 || (c == 0 && !hiInclusive && toInclusive)) throw new IllegalArgumentException("key out of range"); } } return new SubMap(m, fromKey, fromInclusive, toKey, toInclusive, isDescending); } public SubMap subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { if (fromKey == null || toKey == null) throw new NullPointerException(); return newSubMap(fromKey, fromInclusive, toKey, toInclusive); } public SubMap headMap(K toKey, boolean inclusive) { if (toKey == null) throw new NullPointerException(); return newSubMap(null, false, toKey, inclusive); } public SubMap tailMap(K fromKey, boolean inclusive) { if (fromKey == null) throw new NullPointerException(); return newSubMap(fromKey, inclusive, null, false); } public SubMap subMap(K fromKey, K toKey) { return subMap(fromKey, true, toKey, false); } public SubMap headMap(K toKey) { return headMap(toKey, false); } public SubMap tailMap(K fromKey) { return tailMap(fromKey, true); } public SubMap descendingMap() { return new SubMap(m, lo, loInclusive, hi, hiInclusive, !isDescending); } /* ---------------- Relational methods -------------- */ public Map.Entry ceilingEntry(K key) { return getNearEntry(key, GT|EQ); } public K ceilingKey(K key) { return getNearKey(key, GT|EQ); } public Map.Entry lowerEntry(K key) { return getNearEntry(key, LT); } public K lowerKey(K key) { return getNearKey(key, LT); } public Map.Entry floorEntry(K key) { return getNearEntry(key, LT|EQ); } public K floorKey(K key) { return getNearKey(key, LT|EQ); } public Map.Entry higherEntry(K key) { return getNearEntry(key, GT); } public K higherKey(K key) { return getNearKey(key, GT); } public K firstKey() { return isDescending ? highestKey() : lowestKey(); } public K lastKey() { return isDescending ? lowestKey() : highestKey(); } public Map.Entry firstEntry() { return isDescending ? highestEntry() : lowestEntry(); } public Map.Entry lastEntry() { return isDescending ? lowestEntry() : highestEntry(); } public Map.Entry pollFirstEntry() { return isDescending ? removeHighest() : removeLowest(); } public Map.Entry pollLastEntry() { return isDescending ? removeLowest() : removeHighest(); } /* ---------------- Submap Views -------------- */ public NavigableSet keySet() { KeySet ks; if ((ks = keySetView) != null) return ks; return keySetView = new KeySet<>(this); } public NavigableSet navigableKeySet() { KeySet ks; if ((ks = keySetView) != null) return ks; return keySetView = new KeySet<>(this); } public Collection values() { Values vs; if ((vs = valuesView) != null) return vs; return valuesView = new Values<>(this); } public Set> entrySet() { EntrySet es; if ((es = entrySetView) != null) return es; return entrySetView = new EntrySet(this); } public NavigableSet descendingKeySet() { return descendingMap().navigableKeySet(); } /** * Variant of main Iter class to traverse through submaps. * Also serves as back-up Spliterator for views. */ abstract class SubMapIter implements Iterator, Spliterator { /** the last node returned by next() */ Node lastReturned; /** the next node to return from next(); */ Node next; /** Cache of next value field to maintain weak consistency */ V nextValue; SubMapIter() { VarHandle.acquireFence(); Comparator cmp = m.comparator; for (;;) { next = isDescending ? hiNode(cmp) : loNode(cmp); if (next == null) break; V x = next.val; if (x != null) { if (! inBounds(next.key, cmp)) next = null; else nextValue = x; break; } } } public final boolean hasNext() { return next != null; } final void advance() { if (next == null) throw new NoSuchElementException(); lastReturned = next; if (isDescending) descend(); else ascend(); } private void ascend() { Comparator cmp = m.comparator; for (;;) { next = next.next; if (next == null) break; V x = next.val; if (x != null) { if (tooHigh(next.key, cmp)) next = null; else nextValue = x; break; } } } private void descend() { Comparator cmp = m.comparator; for (;;) { next = m.findNear(lastReturned.key, LT, cmp); if (next == null) break; V x = next.val; if (x != null) { if (tooLow(next.key, cmp)) next = null; else nextValue = x; break; } } } public void remove() { Node l = lastReturned; if (l == null) throw new IllegalStateException(); m.remove(l.key); lastReturned = null; } public Spliterator trySplit() { return null; } public boolean tryAdvance(Consumer action) { if (hasNext()) { action.accept(next()); return true; } return false; } public void forEachRemaining(Consumer action) { while (hasNext()) action.accept(next()); } public long estimateSize() { return Long.MAX_VALUE; } } final class SubMapValueIterator extends SubMapIter { public V next() { V v = nextValue; advance(); return v; } public int characteristics() { return 0; } } final class SubMapKeyIterator extends SubMapIter { public K next() { Node n = next; advance(); return n.key; } public int characteristics() { return Spliterator.DISTINCT | Spliterator.ORDERED | Spliterator.SORTED; } public final Comparator getComparator() { return SubMap.this.comparator(); } } final class SubMapEntryIterator extends SubMapIter> { public Map.Entry next() { Node n = next; V v = nextValue; advance(); return new AbstractMap.SimpleImmutableEntry(n.key, v); } public int characteristics() { return Spliterator.DISTINCT; } } } // default Map method overrides public void forEach(BiConsumer action) { if (action == null) throw new NullPointerException(); Node b, n; V v; if ((b = baseHead()) != null) { while ((n = b.next) != null) { if ((v = n.val) != null) action.accept(n.key, v); b = n; } } } public void replaceAll(BiFunction function) { if (function == null) throw new NullPointerException(); Node b, n; V v; if ((b = baseHead()) != null) { while ((n = b.next) != null) { while ((v = n.val) != null) { V r = function.apply(n.key, v); if (r == null) throw new NullPointerException(); if (VAL.compareAndSet(n, v, r)) break; } b = n; } } } /** * Helper method for EntrySet.removeIf. */ boolean removeEntryIf(Predicate> function) { if (function == null) throw new NullPointerException(); boolean removed = false; Node b, n; V v; if ((b = baseHead()) != null) { while ((n = b.next) != null) { if ((v = n.val) != null) { K k = n.key; Map.Entry e = new AbstractMap.SimpleImmutableEntry<>(k, v); if (function.test(e) && remove(k, v)) removed = true; } b = n; } } return removed; } /** * Helper method for Values.removeIf. */ boolean removeValueIf(Predicate function) { if (function == null) throw new NullPointerException(); boolean removed = false; Node b, n; V v; if ((b = baseHead()) != null) { while ((n = b.next) != null) { if ((v = n.val) != null && function.test(v) && remove(n.key, v)) removed = true; b = n; } } return removed; } /** * Base class providing common structure for Spliterators. * (Although not all that much common functionality; as usual for * view classes, details annoyingly vary in key, value, and entry * subclasses in ways that are not worth abstracting out for * internal classes.) * * The basic split strategy is to recursively descend from top * level, row by row, descending to next row when either split * off, or the end of row is encountered. Control of the number of * splits relies on some statistical estimation: The expected * remaining number of elements of a skip list when advancing * either across or down decreases by about 25%. */ abstract static class CSLMSpliterator { final Comparator comparator; final K fence; // exclusive upper bound for keys, or null if to end Index row; // the level to split out Node current; // current traversal node; initialize at origin long est; // size estimate CSLMSpliterator(Comparator comparator, Index row, Node origin, K fence, long est) { this.comparator = comparator; this.row = row; this.current = origin; this.fence = fence; this.est = est; } public final long estimateSize() { return est; } } static final class KeySpliterator extends CSLMSpliterator implements Spliterator { KeySpliterator(Comparator comparator, Index row, Node origin, K fence, long est) { super(comparator, row, origin, fence, est); } public KeySpliterator trySplit() { Node e; K ek; Comparator cmp = comparator; K f = fence; if ((e = current) != null && (ek = e.key) != null) { for (Index q = row; q != null; q = row = q.down) { Index s; Node b, n; K sk; if ((s = q.right) != null && (b = s.node) != null && (n = b.next) != null && n.val != null && (sk = n.key) != null && cpr(cmp, sk, ek) > 0 && (f == null || cpr(cmp, sk, f) < 0)) { current = n; Index r = q.down; row = (s.right != null) ? s : s.down; est -= est >>> 2; return new KeySpliterator(cmp, r, e, sk, est); } } } return null; } public void forEachRemaining(Consumer action) { if (action == null) throw new NullPointerException(); Comparator cmp = comparator; K f = fence; Node e = current; current = null; for (; e != null; e = e.next) { K k; if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) break; if (e.val != null) action.accept(k); } } public boolean tryAdvance(Consumer action) { if (action == null) throw new NullPointerException(); Comparator cmp = comparator; K f = fence; Node e = current; for (; e != null; e = e.next) { K k; if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) { e = null; break; } if (e.val != null) { current = e.next; action.accept(k); return true; } } current = e; return false; } public int characteristics() { return Spliterator.DISTINCT | Spliterator.SORTED | Spliterator.ORDERED | Spliterator.CONCURRENT | Spliterator.NONNULL; } public final Comparator getComparator() { return comparator; } } // factory method for KeySpliterator final KeySpliterator keySpliterator() { Index h; Node n; long est; VarHandle.acquireFence(); if ((h = head) == null) { n = null; est = 0L; } else { n = h.node; est = getAdderCount(); } return new KeySpliterator(comparator, h, n, null, est); } static final class ValueSpliterator extends CSLMSpliterator implements Spliterator { ValueSpliterator(Comparator comparator, Index row, Node origin, K fence, long est) { super(comparator, row, origin, fence, est); } public ValueSpliterator trySplit() { Node e; K ek; Comparator cmp = comparator; K f = fence; if ((e = current) != null && (ek = e.key) != null) { for (Index q = row; q != null; q = row = q.down) { Index s; Node b, n; K sk; if ((s = q.right) != null && (b = s.node) != null && (n = b.next) != null && n.val != null && (sk = n.key) != null && cpr(cmp, sk, ek) > 0 && (f == null || cpr(cmp, sk, f) < 0)) { current = n; Index r = q.down; row = (s.right != null) ? s : s.down; est -= est >>> 2; return new ValueSpliterator(cmp, r, e, sk, est); } } } return null; } public void forEachRemaining(Consumer action) { if (action == null) throw new NullPointerException(); Comparator cmp = comparator; K f = fence; Node e = current; current = null; for (; e != null; e = e.next) { K k; V v; if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) break; if ((v = e.val) != null) action.accept(v); } } public boolean tryAdvance(Consumer action) { if (action == null) throw new NullPointerException(); Comparator cmp = comparator; K f = fence; Node e = current; for (; e != null; e = e.next) { K k; V v; if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) { e = null; break; } if ((v = e.val) != null) { current = e.next; action.accept(v); return true; } } current = e; return false; } public int characteristics() { return Spliterator.CONCURRENT | Spliterator.ORDERED | Spliterator.NONNULL; } } // Almost the same as keySpliterator() final ValueSpliterator valueSpliterator() { Index h; Node n; long est; VarHandle.acquireFence(); if ((h = head) == null) { n = null; est = 0L; } else { n = h.node; est = getAdderCount(); } return new ValueSpliterator(comparator, h, n, null, est); } static final class EntrySpliterator extends CSLMSpliterator implements Spliterator> { EntrySpliterator(Comparator comparator, Index row, Node origin, K fence, long est) { super(comparator, row, origin, fence, est); } public EntrySpliterator trySplit() { Node e; K ek; Comparator cmp = comparator; K f = fence; if ((e = current) != null && (ek = e.key) != null) { for (Index q = row; q != null; q = row = q.down) { Index s; Node b, n; K sk; if ((s = q.right) != null && (b = s.node) != null && (n = b.next) != null && n.val != null && (sk = n.key) != null && cpr(cmp, sk, ek) > 0 && (f == null || cpr(cmp, sk, f) < 0)) { current = n; Index r = q.down; row = (s.right != null) ? s : s.down; est -= est >>> 2; return new EntrySpliterator(cmp, r, e, sk, est); } } } return null; } public void forEachRemaining(Consumer> action) { if (action == null) throw new NullPointerException(); Comparator cmp = comparator; K f = fence; Node e = current; current = null; for (; e != null; e = e.next) { K k; V v; if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) break; if ((v = e.val) != null) { action.accept (new AbstractMap.SimpleImmutableEntry(k, v)); } } } public boolean tryAdvance(Consumer> action) { if (action == null) throw new NullPointerException(); Comparator cmp = comparator; K f = fence; Node e = current; for (; e != null; e = e.next) { K k; V v; if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) { e = null; break; } if ((v = e.val) != null) { current = e.next; action.accept (new AbstractMap.SimpleImmutableEntry(k, v)); return true; } } current = e; return false; } public int characteristics() { return Spliterator.DISTINCT | Spliterator.SORTED | Spliterator.ORDERED | Spliterator.CONCURRENT | Spliterator.NONNULL; } public final Comparator> getComparator() { // Adapt or create a key-based comparator if (comparator != null) { return Map.Entry.comparingByKey(comparator); } else { return (Comparator> & Serializable) (e1, e2) -> { @SuppressWarnings("unchecked") Comparable k1 = (Comparable) e1.getKey(); return k1.compareTo(e2.getKey()); }; } } } // Almost the same as keySpliterator() final EntrySpliterator entrySpliterator() { Index h; Node n; long est; VarHandle.acquireFence(); if ((h = head) == null) { n = null; est = 0L; } else { n = h.node; est = getAdderCount(); } return new EntrySpliterator(comparator, h, n, null, est); } // VarHandle mechanics private static final VarHandle HEAD; private static final VarHandle ADDER; private static final VarHandle NEXT; private static final VarHandle VAL; private static final VarHandle RIGHT; static { try { MethodHandles.Lookup l = MethodHandles.lookup(); HEAD = l.findVarHandle(ConcurrentSkipListMap.class, "head", Index.class); ADDER = l.findVarHandle(ConcurrentSkipListMap.class, "adder", LongAdder.class); NEXT = l.findVarHandle(Node.class, "next", Node.class); VAL = l.findVarHandle(Node.class, "val", Object.class); RIGHT = l.findVarHandle(Index.class, "right", Index.class); } catch (ReflectiveOperationException e) { throw new Error(e); } } }