From c86ee2657ba63f8d0feb4bb2cedf140a1a48cfab Mon Sep 17 00:00:00 2001 From: noackjr Date: Fri, 29 Mar 2019 14:34:29 -0700 Subject: [PATCH] Remove unused configuration of load factor This allows removal of loadFactor/threshold which saves 8 bytes per object. While here: - Reduce diff between Map/Set classes - Make base forEach() work for Linked classes Shallow object size (bytes): - CompactHashMap: 64 -> 56 - CompactLinkedHashMap: 80 -> 72 - CompactHashSet: 40 -> 32 - CompactLinkedHashSet: 56 -> 48 RELNOTES=n/a ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=241053148 --- .../google/common/collect/CompactHashMap.java | 106 +++++------------ .../google/common/collect/CompactHashSet.java | 100 ++++++---------- .../common/collect/CompactLinkedHashMap.java | 23 ++-- .../common/collect/CompactLinkedHashSet.java | 95 ++++++++------- .../google/common/collect/CompactHashMap.java | 112 ++++++------------ .../google/common/collect/CompactHashSet.java | 102 ++++++---------- .../common/collect/CompactLinkedHashMap.java | 51 ++------ .../common/collect/CompactLinkedHashSet.java | 104 ++++++++-------- 8 files changed, 273 insertions(+), 420 deletions(-) diff --git a/android/guava/src/com/google/common/collect/CompactHashMap.java b/android/guava/src/com/google/common/collect/CompactHashMap.java index a6667bfcdde0..d945c8eae61b 100644 --- a/android/guava/src/com/google/common/collect/CompactHashMap.java +++ b/android/guava/src/com/google/common/collect/CompactHashMap.java @@ -96,10 +96,7 @@ public static CompactHashMap createWithExpectedSize(int expectedSiz return new CompactHashMap<>(expectedSize); } - private static final int MAXIMUM_CAPACITY = 1 << 30; - - // TODO(user): decide, and inline, load factor. 0.75? - static final float DEFAULT_LOAD_FACTOR = 1.0f; + private static final float LOAD_FACTOR = 1.0f; /** Bitmask that selects the low 32 bits. */ private static final long NEXT_MASK = (1L << 32) - 1; @@ -143,9 +140,6 @@ public static CompactHashMap createWithExpectedSize(int expectedSiz */ @VisibleForTesting @MonotonicNonNullDecl transient Object[] values; - /** The load factor. */ - transient float loadFactor; - /** * Keeps track of modifications of this set, to make it possible to throw * ConcurrentModificationException in the iterator. Note that we choose not to make this volatile, @@ -153,15 +147,12 @@ public static CompactHashMap createWithExpectedSize(int expectedSiz */ transient int modCount; - /** When we have this many elements, resize the hashtable. */ - private transient int threshold; - /** The number of elements contained in the set. */ private transient int size; /** Constructs a new empty instance of {@code CompactHashMap}. */ CompactHashMap() { - init(DEFAULT_SIZE, DEFAULT_LOAD_FACTOR); + init(DEFAULT_SIZE); } /** @@ -169,27 +160,14 @@ public static CompactHashMap createWithExpectedSize(int expectedSiz * * @param capacity the initial capacity of this {@code CompactHashMap}. */ - CompactHashMap(int capacity) { - init(capacity, DEFAULT_LOAD_FACTOR); - } - - /** - * Constructs a new instance of {@code CompactHashMap} with the specified capacity and load - * factor. - * - * @param capacity the initial capacity of this {@code CompactHashMap}. - * @param loadFactor the load factor of this {@code CompactHashMap}. - */ - CompactHashMap(int capacity, float loadFactor) { - init(capacity, loadFactor); + CompactHashMap(int expectedSize) { + init(expectedSize); } /** Pseudoconstructor for serialization support. */ - void init(int expectedSize, float loadFactor) { + void init(int expectedSize) { Preconditions.checkArgument(expectedSize >= 0, "Initial capacity must be non-negative"); - Preconditions.checkArgument(loadFactor > 0, "Illegal load factor"); - this.loadFactor = loadFactor; - this.threshold = Math.max(1, expectedSize); // Save expectedSize for use in allocArrays() + this.modCount = Math.max(1, expectedSize); // Save expectedSize for use in allocArrays() } /** Returns whether arrays need to be allocated. */ @@ -201,15 +179,13 @@ boolean needsAllocArrays() { void allocArrays() { Preconditions.checkState(needsAllocArrays(), "Arrays already allocated"); - int expectedSize = threshold; - int buckets = Hashing.closedTableSize(expectedSize, loadFactor); + int expectedSize = modCount; + int buckets = Hashing.closedTableSize(expectedSize, LOAD_FACTOR); this.table = newTable(buckets); + this.entries = newEntries(expectedSize); this.keys = new Object[expectedSize]; this.values = new Object[expectedSize]; - - this.entries = newEntries(expectedSize); - this.threshold = Math.max(1, (int) (buckets * loadFactor)); } private static int[] newTable(int size) { @@ -265,7 +241,7 @@ public V put(@NullableDecl K key, @NullableDecl V value) { int tableIndex = hash & hashTableMask(); int newEntryIndex = this.size; // current size, and pointer to the entry to be appended int next = table[tableIndex]; - if (next == UNSET) { + if (next == UNSET) { // uninitialized bucket table[tableIndex] = newEntryIndex; } else { int last; @@ -293,8 +269,9 @@ public V put(@NullableDecl K key, @NullableDecl V value) { resizeMeMaybe(newSize); insertEntry(newEntryIndex, key, value, hash); this.size = newSize; - if (newEntryIndex >= threshold) { - resizeTable(2 * table.length); + int oldCapacity = table.length; + if (Hashing.needsResizing(newEntryIndex, oldCapacity, LOAD_FACTOR)) { + resizeTable(2 * oldCapacity); } modCount++; return null; @@ -340,13 +317,6 @@ void resizeEntries(int newCapacity) { } private void resizeTable(int newCapacity) { // newCapacity always a power of two - int[] oldTable = table; - int oldCapacity = oldTable.length; - if (oldCapacity >= MAXIMUM_CAPACITY) { - threshold = Integer.MAX_VALUE; - return; - } - int newThreshold = 1 + (int) (newCapacity * loadFactor); int[] newTable = newTable(newCapacity); long[] entries = this.entries; @@ -360,7 +330,6 @@ private void resizeTable(int newCapacity) { // newCapacity always a power of two entries[i] = ((long) hash << 32) | (NEXT_MASK & next); } - this.threshold = newThreshold; this.table = newTable; } @@ -412,25 +381,23 @@ private V remove(@NullableDecl Object key, int hash) { } int last = UNSET; do { - if (getHash(entries[next]) == hash) { - if (Objects.equal(key, keys[next])) { - @SuppressWarnings("unchecked") // values only contains Vs - @NullableDecl - V oldValue = (V) values[next]; - - if (last == UNSET) { - // we need to update the root link from table[] - table[tableIndex] = getNext(entries[next]); - } else { - // we need to update the link from the chain - entries[last] = swapNext(entries[last], getNext(entries[next])); - } - - moveLastEntry(next); - size--; - modCount++; - return oldValue; + if (getHash(entries[next]) == hash && Objects.equal(key, keys[next])) { + @SuppressWarnings("unchecked") // values only contains Vs + @NullableDecl + V oldValue = (V) values[next]; + + if (last == UNSET) { + // we need to update the root link from table[] + table[tableIndex] = getNext(entries[next]); + } else { + // we need to update the link from the chain + entries[last] = swapNext(entries[last], getNext(entries[next])); } + + moveLastEntry(next); + size--; + modCount++; + return oldValue; } last = next; next = getNext(entries[next]); @@ -777,18 +744,7 @@ public void trimToSize() { if (size < entries.length) { resizeEntries(size); } - // size / loadFactor gives the table size of the appropriate load factor, - // but that may not be a power of two. We floor it to a power of two by - // keeping its highest bit. But the smaller table may have a load factor - // larger than what we want; then we want to go to the next power of 2 if we can - int minimumTableSize = Math.max(1, Integer.highestOneBit((int) (size / loadFactor))); - if (minimumTableSize < MAXIMUM_CAPACITY) { - double load = (double) size / minimumTableSize; - if (load > loadFactor) { - minimumTableSize <<= 1; // increase to next power if possible - } - } - + int minimumTableSize = Hashing.closedTableSize(size, LOAD_FACTOR); if (minimumTableSize < table.length) { resizeTable(minimumTableSize); } @@ -827,7 +783,7 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo if (elementCount < 0) { throw new InvalidObjectException("Invalid size: " + elementCount); } - init(elementCount, DEFAULT_LOAD_FACTOR); + init(elementCount); for (int i = 0; i < elementCount; i++) { K key = (K) stream.readObject(); V value = (V) stream.readObject(); diff --git a/android/guava/src/com/google/common/collect/CompactHashSet.java b/android/guava/src/com/google/common/collect/CompactHashSet.java index 52521e5e0738..4da8bd2a171a 100644 --- a/android/guava/src/com/google/common/collect/CompactHashSet.java +++ b/android/guava/src/com/google/common/collect/CompactHashSet.java @@ -71,7 +71,7 @@ class CompactHashSet extends AbstractSet implements Serializable { /** Creates an empty {@code CompactHashSet} instance. */ public static CompactHashSet create() { - return new CompactHashSet(); + return new CompactHashSet<>(); } /** @@ -110,13 +110,10 @@ public static CompactHashSet create(E... elements) { * @throws IllegalArgumentException if {@code expectedSize} is negative */ public static CompactHashSet createWithExpectedSize(int expectedSize) { - return new CompactHashSet(expectedSize); + return new CompactHashSet<>(expectedSize); } - private static final int MAXIMUM_CAPACITY = 1 << 30; - - // TODO(user): decide, and inline, load factor. 0.75? - private static final float DEFAULT_LOAD_FACTOR = 1.0f; + private static final float LOAD_FACTOR = 1.0f; /** Bitmask that selects the low 32 bits. */ private static final long NEXT_MASK = (1L << 32) - 1; @@ -127,10 +124,11 @@ public static CompactHashSet createWithExpectedSize(int expectedSize) { // TODO(user): decide default size @VisibleForTesting static final int DEFAULT_SIZE = 3; + // used to indicate blank table entries static final int UNSET = -1; /** - * The hashtable. Its values are indexes to both the elements and entries arrays. + * The hashtable. Its values are indexes to the elements and entries arrays. * *

Currently, the UNSET value means "null pointer", and any non negative value x is the actual * index. @@ -147,12 +145,12 @@ public static CompactHashSet createWithExpectedSize(int expectedSize) { */ @MonotonicNonNullDecl private transient long[] entries; - /** The elements contained in the set, in the range of [0, size()). */ + /** + * The elements contained in the set, in the range of [0, size()). The elements in [size(), + * elements.length) are all {@code null}. + */ @MonotonicNonNullDecl transient Object[] elements; - /** The load factor. */ - transient float loadFactor; - /** * Keeps track of modifications of this set, to make it possible to throw * ConcurrentModificationException in the iterator. Note that we choose not to make this volatile, @@ -160,15 +158,12 @@ public static CompactHashSet createWithExpectedSize(int expectedSize) { */ transient int modCount; - /** When we have this many elements, resize the hashtable. */ - private transient int threshold; - /** The number of elements contained in the set. */ private transient int size; /** Constructs a new empty instance of {@code CompactHashSet}. */ CompactHashSet() { - init(DEFAULT_SIZE, DEFAULT_LOAD_FACTOR); + init(DEFAULT_SIZE); } /** @@ -177,15 +172,13 @@ public static CompactHashSet createWithExpectedSize(int expectedSize) { * @param expectedSize the initial capacity of this {@code CompactHashSet}. */ CompactHashSet(int expectedSize) { - init(expectedSize, DEFAULT_LOAD_FACTOR); + init(expectedSize); } /** Pseudoconstructor for serialization support. */ - void init(int expectedSize, float loadFactor) { + void init(int expectedSize) { Preconditions.checkArgument(expectedSize >= 0, "Initial capacity must be non-negative"); - Preconditions.checkArgument(loadFactor > 0, "Illegal load factor"); - this.loadFactor = loadFactor; - this.threshold = Math.max(1, expectedSize); // Save expectedSize for use in allocArrays() + this.modCount = Math.max(1, expectedSize); // Save expectedSize for use in allocArrays() } /** Returns whether arrays need to be allocated. */ @@ -197,14 +190,12 @@ boolean needsAllocArrays() { void allocArrays() { Preconditions.checkState(needsAllocArrays(), "Arrays already allocated"); - int expectedSize = threshold; - int buckets = Hashing.closedTableSize(expectedSize, loadFactor); + int expectedSize = modCount; + int buckets = Hashing.closedTableSize(expectedSize, LOAD_FACTOR); this.table = newTable(buckets); - this.elements = new Object[expectedSize]; - this.entries = newEntries(expectedSize); - this.threshold = Math.max(1, (int) (buckets * loadFactor)); + this.elements = new Object[expectedSize]; } private static int[] newTable(int size) { @@ -219,6 +210,10 @@ private static long[] newEntries(int size) { return array; } + private int hashTableMask() { + return table.length - 1; + } + private static int getHash(long entry) { return (int) (entry >>> 32); } @@ -233,10 +228,6 @@ private static long swapNext(long entry, int newNext) { return (HASH_MASK & entry) | (NEXT_MASK & newNext); } - private int hashTableMask() { - return table.length - 1; - } - @CanIgnoreReturnValue @Override public boolean add(@NullableDecl E object) { @@ -245,6 +236,7 @@ public boolean add(@NullableDecl E object) { } long[] entries = this.entries; Object[] elements = this.elements; + int hash = smearedHash(object); int tableIndex = hash & hashTableMask(); int newEntryIndex = this.size; // current size, and pointer to the entry to be appended @@ -271,8 +263,9 @@ public boolean add(@NullableDecl E object) { resizeMeMaybe(newSize); insertEntry(newEntryIndex, object, hash); this.size = newSize; - if (newEntryIndex >= threshold) { - resizeTable(2 * table.length); + int oldCapacity = table.length; + if (Hashing.needsResizing(newEntryIndex, oldCapacity, LOAD_FACTOR)) { + resizeTable(2 * oldCapacity); } modCount++; return true; @@ -307,22 +300,15 @@ private void resizeMeMaybe(int newSize) { void resizeEntries(int newCapacity) { this.elements = Arrays.copyOf(elements, newCapacity); long[] entries = this.entries; - int oldSize = entries.length; + int oldCapacity = entries.length; entries = Arrays.copyOf(entries, newCapacity); - if (newCapacity > oldSize) { - Arrays.fill(entries, oldSize, newCapacity, UNSET); + if (newCapacity > oldCapacity) { + Arrays.fill(entries, oldCapacity, newCapacity, UNSET); } this.entries = entries; } private void resizeTable(int newCapacity) { // newCapacity always a power of two - int[] oldTable = table; - int oldCapacity = oldTable.length; - if (oldCapacity >= MAXIMUM_CAPACITY) { - threshold = Integer.MAX_VALUE; - return; - } - int newThreshold = 1 + (int) (newCapacity * loadFactor); int[] newTable = newTable(newCapacity); long[] entries = this.entries; @@ -336,7 +322,6 @@ private void resizeTable(int newCapacity) { // newCapacity always a power of two entries[i] = ((long) hash << 32) | (NEXT_MASK & next); } - this.threshold = newThreshold; this.table = newTable; } @@ -384,7 +369,7 @@ private boolean remove(Object object, int hash) { entries[last] = swapNext(entries[last], getNext(entries[next])); } - moveEntry(next); + moveLastEntry(next); size--; modCount++; return true; @@ -398,7 +383,7 @@ private boolean remove(Object object, int hash) { /** * Moves the last entry in the entry array into {@code dstIndex}, and nulls out its old position. */ - void moveEntry(int dstIndex) { + void moveLastEntry(int dstIndex) { int srcIndex = size() - 1; if (dstIndex < srcIndex) { // move last entry to deleted spot @@ -455,12 +440,12 @@ int adjustAfterRemove(int indexBeforeRemove, @SuppressWarnings("unused") int ind public Iterator iterator() { return new Iterator() { int expectedModCount = modCount; - int index = firstEntryIndex(); + int currentIndex = firstEntryIndex(); int indexToRemove = -1; @Override public boolean hasNext() { - return index >= 0; + return currentIndex >= 0; } @Override @@ -470,9 +455,9 @@ public E next() { if (!hasNext()) { throw new NoSuchElementException(); } - indexToRemove = index; - E result = (E) elements[index]; - index = getSuccessor(index); + indexToRemove = currentIndex; + E result = (E) elements[currentIndex]; + currentIndex = getSuccessor(currentIndex); return result; } @@ -482,7 +467,7 @@ public void remove() { checkRemove(indexToRemove >= 0); expectedModCount++; CompactHashSet.this.remove(elements[indexToRemove], getHash(entries[indexToRemove])); - index = adjustAfterRemove(index, indexToRemove); + currentIndex = adjustAfterRemove(currentIndex, indexToRemove); indexToRemove = -1; } @@ -536,18 +521,7 @@ public void trimToSize() { if (size < entries.length) { resizeEntries(size); } - // size / loadFactor gives the table size of the appropriate load factor, - // but that may not be a power of two. We floor it to a power of two by - // keeping its highest bit. But the smaller table may have a load factor - // larger than what we want; then we want to go to the next power of 2 if we can - int minimumTableSize = Math.max(1, Integer.highestOneBit((int) (size / loadFactor))); - if (minimumTableSize < MAXIMUM_CAPACITY) { - double load = (double) size / minimumTableSize; - if (load > loadFactor) { - minimumTableSize <<= 1; // increase to next power if possible - } - } - + int minimumTableSize = Hashing.closedTableSize(size, LOAD_FACTOR); if (minimumTableSize < table.length) { resizeTable(minimumTableSize); } @@ -584,7 +558,7 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo if (elementCount < 0) { throw new InvalidObjectException("Invalid size: " + elementCount); } - init(elementCount, DEFAULT_LOAD_FACTOR); + init(elementCount); for (int i = 0; i < elementCount; i++) { E element = (E) stream.readObject(); add(element); diff --git a/android/guava/src/com/google/common/collect/CompactLinkedHashMap.java b/android/guava/src/com/google/common/collect/CompactLinkedHashMap.java index 2d3c04c384e4..d24bf37d87d4 100644 --- a/android/guava/src/com/google/common/collect/CompactLinkedHashMap.java +++ b/android/guava/src/com/google/common/collect/CompactLinkedHashMap.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.google.common.collect; import com.google.common.annotations.GwtIncompatible; @@ -51,7 +52,7 @@ public static CompactLinkedHashMap create() { /** * Creates a {@code CompactLinkedHashMap} instance, with a high enough "initial capacity" that it - * should hold {@code expectedSize} elements without growth. + * should hold {@code expectedSize} elements without rebuilding internal data structures. * * @param expectedSize the number of elements you expect to add to the returned set * @return a new, empty {@code CompactLinkedHashMap} with enough capacity to hold {@code @@ -88,26 +89,26 @@ public static CompactLinkedHashMap createWithExpectedSize(int expec } CompactLinkedHashMap(int expectedSize) { - this(expectedSize, DEFAULT_LOAD_FACTOR, false); + this(expectedSize, false); } - CompactLinkedHashMap(int expectedSize, float loadFactor, boolean accessOrder) { - super(expectedSize, loadFactor); + CompactLinkedHashMap(int expectedSize, boolean accessOrder) { + super(expectedSize); this.accessOrder = accessOrder; } @Override - void init(int expectedSize, float loadFactor) { - super.init(expectedSize, loadFactor); - firstEntry = ENDPOINT; - lastEntry = ENDPOINT; + void init(int expectedSize) { + super.init(expectedSize); + this.firstEntry = ENDPOINT; + this.lastEntry = ENDPOINT; } @Override void allocArrays() { super.allocArrays(); int expectedSize = keys.length; // allocated size may be different than initial capacity - links = new long[expectedSize]; + this.links = new long[expectedSize]; Arrays.fill(links, UNSET); } @@ -136,6 +137,7 @@ private void setSucceeds(int pred, int succ) { } else { setSuccessor(pred, succ); } + if (succ == ENDPOINT) { lastEntry = pred; } else { @@ -165,13 +167,14 @@ void accessEntry(int index) { @Override void moveLastEntry(int dstIndex) { int srcIndex = size() - 1; + super.moveLastEntry(dstIndex); + setSucceeds(getPredecessor(dstIndex), getSuccessor(dstIndex)); if (dstIndex < srcIndex) { setSucceeds(getPredecessor(srcIndex), dstIndex); setSucceeds(dstIndex, getSuccessor(srcIndex)); } links[srcIndex] = UNSET; - super.moveLastEntry(dstIndex); } @Override diff --git a/android/guava/src/com/google/common/collect/CompactLinkedHashSet.java b/android/guava/src/com/google/common/collect/CompactLinkedHashSet.java index eb6176f658dc..c3a8f6685c86 100644 --- a/android/guava/src/com/google/common/collect/CompactLinkedHashSet.java +++ b/android/guava/src/com/google/common/collect/CompactLinkedHashSet.java @@ -48,7 +48,7 @@ class CompactLinkedHashSet extends CompactHashSet { /** Creates an empty {@code CompactLinkedHashSet} instance. */ public static CompactLinkedHashSet create() { - return new CompactLinkedHashSet(); + return new CompactLinkedHashSet<>(); } /** @@ -87,7 +87,7 @@ public static CompactLinkedHashSet create(E... elements) { * @throws IllegalArgumentException if {@code expectedSize} is negative */ public static CompactLinkedHashSet createWithExpectedSize(int expectedSize) { - return new CompactLinkedHashSet(expectedSize); + return new CompactLinkedHashSet<>(expectedSize); } private static final int ENDPOINT = -2; @@ -108,7 +108,10 @@ public static CompactLinkedHashSet createWithExpectedSize(int expectedSiz */ @MonotonicNonNullDecl private transient int[] successor; + /** Pointer to the first node in the linked list, or {@code ENDPOINT} if there are no entries. */ private transient int firstEntry; + + /** Pointer to the last node in the linked list, or {@code ENDPOINT} if there are no entries. */ private transient int lastEntry; CompactLinkedHashSet() { @@ -120,10 +123,10 @@ public static CompactLinkedHashSet createWithExpectedSize(int expectedSiz } @Override - void init(int expectedSize, float loadFactor) { - super.init(expectedSize, loadFactor); - firstEntry = ENDPOINT; - lastEntry = ENDPOINT; + void init(int expectedSize) { + super.init(expectedSize); + this.firstEntry = ENDPOINT; + this.lastEntry = ENDPOINT; } @Override @@ -132,65 +135,68 @@ void allocArrays() { int expectedSize = elements.length; // allocated size may be different than initial capacity this.predecessor = new int[expectedSize]; this.successor = new int[expectedSize]; - Arrays.fill(predecessor, UNSET); Arrays.fill(successor, UNSET); } - private void succeeds(int pred, int succ) { + private int getPredecessor(int entry) { + return predecessor[entry]; + } + + @Override + int getSuccessor(int entry) { + return successor[entry]; + } + + private void setSuccessor(int entry, int succ) { + successor[entry] = succ; + } + + private void setPredecessor(int entry, int pred) { + predecessor[entry] = pred; + } + + private void setSucceeds(int pred, int succ) { if (pred == ENDPOINT) { firstEntry = succ; } else { - successor[pred] = succ; + setSuccessor(pred, succ); } if (succ == ENDPOINT) { lastEntry = pred; } else { - predecessor[succ] = pred; + setPredecessor(succ, pred); } } @Override void insertEntry(int entryIndex, E object, int hash) { super.insertEntry(entryIndex, object, hash); - succeeds(lastEntry, entryIndex); - succeeds(entryIndex, ENDPOINT); + setSucceeds(lastEntry, entryIndex); + setSucceeds(entryIndex, ENDPOINT); } @Override - void moveEntry(int dstIndex) { + void moveLastEntry(int dstIndex) { int srcIndex = size() - 1; - super.moveEntry(dstIndex); + super.moveLastEntry(dstIndex); - succeeds(predecessor[dstIndex], successor[dstIndex]); - if (srcIndex != dstIndex) { - succeeds(predecessor[srcIndex], dstIndex); - succeeds(dstIndex, successor[srcIndex]); + setSucceeds(getPredecessor(dstIndex), getSuccessor(dstIndex)); + if (dstIndex < srcIndex) { + setSucceeds(getPredecessor(srcIndex), dstIndex); + setSucceeds(dstIndex, getSuccessor(srcIndex)); } predecessor[srcIndex] = UNSET; successor[srcIndex] = UNSET; } - @Override - public void clear() { - if (needsAllocArrays()) { - return; - } - firstEntry = ENDPOINT; - lastEntry = ENDPOINT; - Arrays.fill(predecessor, 0, size(), UNSET); - Arrays.fill(successor, 0, size(), UNSET); - super.clear(); - } - @Override void resizeEntries(int newCapacity) { super.resizeEntries(newCapacity); int oldCapacity = predecessor.length; predecessor = Arrays.copyOf(predecessor, newCapacity); successor = Arrays.copyOf(successor, newCapacity); - if (oldCapacity < newCapacity) { Arrays.fill(predecessor, oldCapacity, newCapacity, UNSET); Arrays.fill(successor, oldCapacity, newCapacity, UNSET); @@ -198,27 +204,34 @@ void resizeEntries(int newCapacity) { } @Override - public Object[] toArray() { - return ObjectArrays.toArrayImpl(this); + int firstEntryIndex() { + return firstEntry; } @Override - public T[] toArray(T[] a) { - return ObjectArrays.toArrayImpl(this, a); + int adjustAfterRemove(int indexBeforeRemove, int indexRemoved) { + return (indexBeforeRemove >= size()) ? indexRemoved : indexBeforeRemove; } @Override - int firstEntryIndex() { - return firstEntry; + public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); } @Override - int adjustAfterRemove(int indexBeforeRemove, int indexRemoved) { - return (indexBeforeRemove == size()) ? indexRemoved : indexBeforeRemove; + public T[] toArray(T[] a) { + return ObjectArrays.toArrayImpl(this, a); } @Override - int getSuccessor(int entryIndex) { - return successor[entryIndex]; + public void clear() { + if (needsAllocArrays()) { + return; + } + this.firstEntry = ENDPOINT; + this.lastEntry = ENDPOINT; + Arrays.fill(predecessor, 0, size(), UNSET); + Arrays.fill(successor, 0, size(), UNSET); + super.clear(); } } diff --git a/guava/src/com/google/common/collect/CompactHashMap.java b/guava/src/com/google/common/collect/CompactHashMap.java index 99cb2164d4ae..7711a1673983 100644 --- a/guava/src/com/google/common/collect/CompactHashMap.java +++ b/guava/src/com/google/common/collect/CompactHashMap.java @@ -101,10 +101,7 @@ public static CompactHashMap createWithExpectedSize(int expectedSiz return new CompactHashMap<>(expectedSize); } - private static final int MAXIMUM_CAPACITY = 1 << 30; - - // TODO(user): decide, and inline, load factor. 0.75? - static final float DEFAULT_LOAD_FACTOR = 1.0f; + private static final float LOAD_FACTOR = 1.0f; /** Bitmask that selects the low 32 bits. */ private static final long NEXT_MASK = (1L << 32) - 1; @@ -148,9 +145,6 @@ public static CompactHashMap createWithExpectedSize(int expectedSiz */ @VisibleForTesting transient Object @MonotonicNonNull [] values; - /** The load factor. */ - transient float loadFactor; - /** * Keeps track of modifications of this set, to make it possible to throw * ConcurrentModificationException in the iterator. Note that we choose not to make this volatile, @@ -158,15 +152,12 @@ public static CompactHashMap createWithExpectedSize(int expectedSiz */ transient int modCount; - /** When we have this many elements, resize the hashtable. */ - private transient int threshold; - /** The number of elements contained in the set. */ private transient int size; /** Constructs a new empty instance of {@code CompactHashMap}. */ CompactHashMap() { - init(DEFAULT_SIZE, DEFAULT_LOAD_FACTOR); + init(DEFAULT_SIZE); } /** @@ -174,27 +165,14 @@ public static CompactHashMap createWithExpectedSize(int expectedSiz * * @param capacity the initial capacity of this {@code CompactHashMap}. */ - CompactHashMap(int capacity) { - init(capacity, DEFAULT_LOAD_FACTOR); - } - - /** - * Constructs a new instance of {@code CompactHashMap} with the specified capacity and load - * factor. - * - * @param capacity the initial capacity of this {@code CompactHashMap}. - * @param loadFactor the load factor of this {@code CompactHashMap}. - */ - CompactHashMap(int capacity, float loadFactor) { - init(capacity, loadFactor); + CompactHashMap(int expectedSize) { + init(expectedSize); } /** Pseudoconstructor for serialization support. */ - void init(int expectedSize, float loadFactor) { + void init(int expectedSize) { Preconditions.checkArgument(expectedSize >= 0, "Initial capacity must be non-negative"); - Preconditions.checkArgument(loadFactor > 0, "Illegal load factor"); - this.loadFactor = loadFactor; - this.threshold = Math.max(1, expectedSize); // Save expectedSize for use in allocArrays() + this.modCount = Math.max(1, expectedSize); // Save expectedSize for use in allocArrays() } /** Returns whether arrays need to be allocated. */ @@ -206,15 +184,13 @@ boolean needsAllocArrays() { void allocArrays() { Preconditions.checkState(needsAllocArrays(), "Arrays already allocated"); - int expectedSize = threshold; - int buckets = Hashing.closedTableSize(expectedSize, loadFactor); + int expectedSize = modCount; + int buckets = Hashing.closedTableSize(expectedSize, LOAD_FACTOR); this.table = newTable(buckets); + this.entries = newEntries(expectedSize); this.keys = new Object[expectedSize]; this.values = new Object[expectedSize]; - - this.entries = newEntries(expectedSize); - this.threshold = Math.max(1, (int) (buckets * loadFactor)); } private static int[] newTable(int size) { @@ -269,7 +245,7 @@ void accessEntry(int index) { int tableIndex = hash & hashTableMask(); int newEntryIndex = this.size; // current size, and pointer to the entry to be appended int next = table[tableIndex]; - if (next == UNSET) { + if (next == UNSET) { // uninitialized bucket table[tableIndex] = newEntryIndex; } else { int last; @@ -297,8 +273,9 @@ void accessEntry(int index) { resizeMeMaybe(newSize); insertEntry(newEntryIndex, key, value, hash); this.size = newSize; - if (newEntryIndex >= threshold) { - resizeTable(2 * table.length); + int oldCapacity = table.length; + if (Hashing.needsResizing(newEntryIndex, oldCapacity, LOAD_FACTOR)) { + resizeTable(2 * oldCapacity); } modCount++; return null; @@ -344,13 +321,6 @@ void resizeEntries(int newCapacity) { } private void resizeTable(int newCapacity) { // newCapacity always a power of two - int[] oldTable = table; - int oldCapacity = oldTable.length; - if (oldCapacity >= MAXIMUM_CAPACITY) { - threshold = Integer.MAX_VALUE; - return; - } - int newThreshold = 1 + (int) (newCapacity * loadFactor); int[] newTable = newTable(newCapacity); long[] entries = this.entries; @@ -364,7 +334,6 @@ private void resizeTable(int newCapacity) { // newCapacity always a power of two entries[i] = ((long) hash << 32) | (NEXT_MASK & next); } - this.threshold = newThreshold; this.table = newTable; } @@ -414,25 +383,23 @@ public V get(@Nullable Object key) { } int last = UNSET; do { - if (getHash(entries[next]) == hash) { - if (Objects.equal(key, keys[next])) { - @SuppressWarnings("unchecked") // values only contains Vs - @Nullable - V oldValue = (V) values[next]; - - if (last == UNSET) { - // we need to update the root link from table[] - table[tableIndex] = getNext(entries[next]); - } else { - // we need to update the link from the chain - entries[last] = swapNext(entries[last], getNext(entries[next])); - } - - moveLastEntry(next); - size--; - modCount++; - return oldValue; + if (getHash(entries[next]) == hash && Objects.equal(key, keys[next])) { + @SuppressWarnings("unchecked") // values only contains Vs + @Nullable + V oldValue = (V) values[next]; + + if (last == UNSET) { + // we need to update the root link from table[] + table[tableIndex] = getNext(entries[next]); + } else { + // we need to update the link from the chain + entries[last] = swapNext(entries[last], getNext(entries[next])); } + + moveLastEntry(next); + size--; + modCount++; + return oldValue; } last = next; next = getNext(entries[next]); @@ -616,7 +583,7 @@ public Spliterator spliterator() { @Override public void forEach(Consumer action) { checkNotNull(action); - for (int i = 0; i < size; i++) { + for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { action.accept((K) keys[i]); } } @@ -635,7 +602,7 @@ K getOutput(int entry) { @Override public void forEach(BiConsumer action) { checkNotNull(action); - for (int i = 0; i < size; i++) { + for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { action.accept((K) keys[i], (V) values[i]); } } @@ -793,7 +760,7 @@ public Iterator iterator() { @Override public void forEach(Consumer action) { checkNotNull(action); - for (int i = 0; i < size; i++) { + for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { action.accept((V) values[i]); } } @@ -848,18 +815,7 @@ public void trimToSize() { if (size < entries.length) { resizeEntries(size); } - // size / loadFactor gives the table size of the appropriate load factor, - // but that may not be a power of two. We floor it to a power of two by - // keeping its highest bit. But the smaller table may have a load factor - // larger than what we want; then we want to go to the next power of 2 if we can - int minimumTableSize = Math.max(1, Integer.highestOneBit((int) (size / loadFactor))); - if (minimumTableSize < MAXIMUM_CAPACITY) { - double load = (double) size / minimumTableSize; - if (load > loadFactor) { - minimumTableSize <<= 1; // increase to next power if possible - } - } - + int minimumTableSize = Hashing.closedTableSize(size, LOAD_FACTOR); if (minimumTableSize < table.length) { resizeTable(minimumTableSize); } @@ -898,7 +854,7 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo if (elementCount < 0) { throw new InvalidObjectException("Invalid size: " + elementCount); } - init(elementCount, DEFAULT_LOAD_FACTOR); + init(elementCount); for (int i = 0; i < elementCount; i++) { K key = (K) stream.readObject(); V value = (V) stream.readObject(); diff --git a/guava/src/com/google/common/collect/CompactHashSet.java b/guava/src/com/google/common/collect/CompactHashSet.java index 05276b675fab..3d1ff5236f96 100644 --- a/guava/src/com/google/common/collect/CompactHashSet.java +++ b/guava/src/com/google/common/collect/CompactHashSet.java @@ -75,7 +75,7 @@ class CompactHashSet extends AbstractSet implements Serializable { /** Creates an empty {@code CompactHashSet} instance. */ public static CompactHashSet create() { - return new CompactHashSet(); + return new CompactHashSet<>(); } /** @@ -114,13 +114,10 @@ public static CompactHashSet create(E... elements) { * @throws IllegalArgumentException if {@code expectedSize} is negative */ public static CompactHashSet createWithExpectedSize(int expectedSize) { - return new CompactHashSet(expectedSize); + return new CompactHashSet<>(expectedSize); } - private static final int MAXIMUM_CAPACITY = 1 << 30; - - // TODO(user): decide, and inline, load factor. 0.75? - private static final float DEFAULT_LOAD_FACTOR = 1.0f; + private static final float LOAD_FACTOR = 1.0f; /** Bitmask that selects the low 32 bits. */ private static final long NEXT_MASK = (1L << 32) - 1; @@ -131,10 +128,11 @@ public static CompactHashSet createWithExpectedSize(int expectedSize) { // TODO(user): decide default size @VisibleForTesting static final int DEFAULT_SIZE = 3; + // used to indicate blank table entries static final int UNSET = -1; /** - * The hashtable. Its values are indexes to both the elements and entries arrays. + * The hashtable. Its values are indexes to the elements and entries arrays. * *

Currently, the UNSET value means "null pointer", and any non negative value x is the actual * index. @@ -151,12 +149,12 @@ public static CompactHashSet createWithExpectedSize(int expectedSize) { */ private transient long @MonotonicNonNull [] entries; - /** The elements contained in the set, in the range of [0, size()). */ + /** + * The elements contained in the set, in the range of [0, size()). The elements in [size(), + * elements.length) are all {@code null}. + */ transient Object @MonotonicNonNull [] elements; - /** The load factor. */ - transient float loadFactor; - /** * Keeps track of modifications of this set, to make it possible to throw * ConcurrentModificationException in the iterator. Note that we choose not to make this volatile, @@ -164,15 +162,12 @@ public static CompactHashSet createWithExpectedSize(int expectedSize) { */ transient int modCount; - /** When we have this many elements, resize the hashtable. */ - private transient int threshold; - /** The number of elements contained in the set. */ private transient int size; /** Constructs a new empty instance of {@code CompactHashSet}. */ CompactHashSet() { - init(DEFAULT_SIZE, DEFAULT_LOAD_FACTOR); + init(DEFAULT_SIZE); } /** @@ -181,15 +176,13 @@ public static CompactHashSet createWithExpectedSize(int expectedSize) { * @param expectedSize the initial capacity of this {@code CompactHashSet}. */ CompactHashSet(int expectedSize) { - init(expectedSize, DEFAULT_LOAD_FACTOR); + init(expectedSize); } /** Pseudoconstructor for serialization support. */ - void init(int expectedSize, float loadFactor) { + void init(int expectedSize) { Preconditions.checkArgument(expectedSize >= 0, "Initial capacity must be non-negative"); - Preconditions.checkArgument(loadFactor > 0, "Illegal load factor"); - this.loadFactor = loadFactor; - this.threshold = Math.max(1, expectedSize); // Save expectedSize for use in allocArrays() + this.modCount = Math.max(1, expectedSize); // Save expectedSize for use in allocArrays() } /** Returns whether arrays need to be allocated. */ @@ -201,14 +194,12 @@ boolean needsAllocArrays() { void allocArrays() { Preconditions.checkState(needsAllocArrays(), "Arrays already allocated"); - int expectedSize = threshold; - int buckets = Hashing.closedTableSize(expectedSize, loadFactor); + int expectedSize = modCount; + int buckets = Hashing.closedTableSize(expectedSize, LOAD_FACTOR); this.table = newTable(buckets); - this.elements = new Object[expectedSize]; - this.entries = newEntries(expectedSize); - this.threshold = Math.max(1, (int) (buckets * loadFactor)); + this.elements = new Object[expectedSize]; } private static int[] newTable(int size) { @@ -223,6 +214,10 @@ private static long[] newEntries(int size) { return array; } + private int hashTableMask() { + return table.length - 1; + } + private static int getHash(long entry) { return (int) (entry >>> 32); } @@ -237,10 +232,6 @@ private static long swapNext(long entry, int newNext) { return (HASH_MASK & entry) | (NEXT_MASK & newNext); } - private int hashTableMask() { - return table.length - 1; - } - @CanIgnoreReturnValue @Override public boolean add(@Nullable E object) { @@ -249,6 +240,7 @@ public boolean add(@Nullable E object) { } long[] entries = this.entries; Object[] elements = this.elements; + int hash = smearedHash(object); int tableIndex = hash & hashTableMask(); int newEntryIndex = this.size; // current size, and pointer to the entry to be appended @@ -275,8 +267,9 @@ public boolean add(@Nullable E object) { resizeMeMaybe(newSize); insertEntry(newEntryIndex, object, hash); this.size = newSize; - if (newEntryIndex >= threshold) { - resizeTable(2 * table.length); + int oldCapacity = table.length; + if (Hashing.needsResizing(newEntryIndex, oldCapacity, LOAD_FACTOR)) { + resizeTable(2 * oldCapacity); } modCount++; return true; @@ -311,22 +304,15 @@ private void resizeMeMaybe(int newSize) { void resizeEntries(int newCapacity) { this.elements = Arrays.copyOf(elements, newCapacity); long[] entries = this.entries; - int oldSize = entries.length; + int oldCapacity = entries.length; entries = Arrays.copyOf(entries, newCapacity); - if (newCapacity > oldSize) { - Arrays.fill(entries, oldSize, newCapacity, UNSET); + if (newCapacity > oldCapacity) { + Arrays.fill(entries, oldCapacity, newCapacity, UNSET); } this.entries = entries; } private void resizeTable(int newCapacity) { // newCapacity always a power of two - int[] oldTable = table; - int oldCapacity = oldTable.length; - if (oldCapacity >= MAXIMUM_CAPACITY) { - threshold = Integer.MAX_VALUE; - return; - } - int newThreshold = 1 + (int) (newCapacity * loadFactor); int[] newTable = newTable(newCapacity); long[] entries = this.entries; @@ -340,7 +326,6 @@ private void resizeTable(int newCapacity) { // newCapacity always a power of two entries[i] = ((long) hash << 32) | (NEXT_MASK & next); } - this.threshold = newThreshold; this.table = newTable; } @@ -388,7 +373,7 @@ private boolean remove(Object object, int hash) { entries[last] = swapNext(entries[last], getNext(entries[next])); } - moveEntry(next); + moveLastEntry(next); size--; modCount++; return true; @@ -402,7 +387,7 @@ private boolean remove(Object object, int hash) { /** * Moves the last entry in the entry array into {@code dstIndex}, and nulls out its old position. */ - void moveEntry(int dstIndex) { + void moveLastEntry(int dstIndex) { int srcIndex = size() - 1; if (dstIndex < srcIndex) { // move last entry to deleted spot @@ -459,12 +444,12 @@ int adjustAfterRemove(int indexBeforeRemove, @SuppressWarnings("unused") int ind public Iterator iterator() { return new Iterator() { int expectedModCount = modCount; - int index = firstEntryIndex(); + int currentIndex = firstEntryIndex(); int indexToRemove = -1; @Override public boolean hasNext() { - return index >= 0; + return currentIndex >= 0; } @Override @@ -474,9 +459,9 @@ public E next() { if (!hasNext()) { throw new NoSuchElementException(); } - indexToRemove = index; - E result = (E) elements[index]; - index = getSuccessor(index); + indexToRemove = currentIndex; + E result = (E) elements[currentIndex]; + currentIndex = getSuccessor(currentIndex); return result; } @@ -486,7 +471,7 @@ public void remove() { checkRemove(indexToRemove >= 0); expectedModCount++; CompactHashSet.this.remove(elements[indexToRemove], getHash(entries[indexToRemove])); - index = adjustAfterRemove(index, indexToRemove); + currentIndex = adjustAfterRemove(currentIndex, indexToRemove); indexToRemove = -1; } @@ -509,7 +494,7 @@ public Spliterator spliterator() { @Override public void forEach(Consumer action) { checkNotNull(action); - for (int i = 0; i < size; i++) { + for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { action.accept((E) elements[i]); } } @@ -556,18 +541,7 @@ public void trimToSize() { if (size < entries.length) { resizeEntries(size); } - // size / loadFactor gives the table size of the appropriate load factor, - // but that may not be a power of two. We floor it to a power of two by - // keeping its highest bit. But the smaller table may have a load factor - // larger than what we want; then we want to go to the next power of 2 if we can - int minimumTableSize = Math.max(1, Integer.highestOneBit((int) (size / loadFactor))); - if (minimumTableSize < MAXIMUM_CAPACITY) { - double load = (double) size / minimumTableSize; - if (load > loadFactor) { - minimumTableSize <<= 1; // increase to next power if possible - } - } - + int minimumTableSize = Hashing.closedTableSize(size, LOAD_FACTOR); if (minimumTableSize < table.length) { resizeTable(minimumTableSize); } @@ -604,7 +578,7 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo if (elementCount < 0) { throw new InvalidObjectException("Invalid size: " + elementCount); } - init(elementCount, DEFAULT_LOAD_FACTOR); + init(elementCount); for (int i = 0; i < elementCount; i++) { E element = (E) stream.readObject(); add(element); diff --git a/guava/src/com/google/common/collect/CompactLinkedHashMap.java b/guava/src/com/google/common/collect/CompactLinkedHashMap.java index 3395f4f9a0dc..1d2c5c8c3cc6 100644 --- a/guava/src/com/google/common/collect/CompactLinkedHashMap.java +++ b/guava/src/com/google/common/collect/CompactLinkedHashMap.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.common.collect; -import static com.google.common.base.Preconditions.checkNotNull; +package com.google.common.collect; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.VisibleForTesting; @@ -25,8 +24,6 @@ import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; -import java.util.function.BiConsumer; -import java.util.function.Consumer; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** @@ -60,7 +57,7 @@ public static CompactLinkedHashMap create() { /** * Creates a {@code CompactLinkedHashMap} instance, with a high enough "initial capacity" that it - * should hold {@code expectedSize} elements without growth. + * should hold {@code expectedSize} elements without rebuilding internal data structures. * * @param expectedSize the number of elements you expect to add to the returned set * @return a new, empty {@code CompactLinkedHashMap} with enough capacity to hold {@code @@ -97,26 +94,26 @@ public static CompactLinkedHashMap createWithExpectedSize(int expec } CompactLinkedHashMap(int expectedSize) { - this(expectedSize, DEFAULT_LOAD_FACTOR, false); + this(expectedSize, false); } - CompactLinkedHashMap(int expectedSize, float loadFactor, boolean accessOrder) { - super(expectedSize, loadFactor); + CompactLinkedHashMap(int expectedSize, boolean accessOrder) { + super(expectedSize); this.accessOrder = accessOrder; } @Override - void init(int expectedSize, float loadFactor) { - super.init(expectedSize, loadFactor); - firstEntry = ENDPOINT; - lastEntry = ENDPOINT; + void init(int expectedSize) { + super.init(expectedSize); + this.firstEntry = ENDPOINT; + this.lastEntry = ENDPOINT; } @Override void allocArrays() { super.allocArrays(); int expectedSize = keys.length; // allocated size may be different than initial capacity - links = new long[expectedSize]; + this.links = new long[expectedSize]; Arrays.fill(links, UNSET); } @@ -145,6 +142,7 @@ private void setSucceeds(int pred, int succ) { } else { setSuccessor(pred, succ); } + if (succ == ENDPOINT) { lastEntry = pred; } else { @@ -174,13 +172,14 @@ void accessEntry(int index) { @Override void moveLastEntry(int dstIndex) { int srcIndex = size() - 1; + super.moveLastEntry(dstIndex); + setSucceeds(getPredecessor(dstIndex), getSuccessor(dstIndex)); if (dstIndex < srcIndex) { setSucceeds(getPredecessor(srcIndex), dstIndex); setSucceeds(dstIndex, getSuccessor(srcIndex)); } links[srcIndex] = UNSET; - super.moveLastEntry(dstIndex); } @Override @@ -203,14 +202,6 @@ int adjustAfterRemove(int indexBeforeRemove, int indexRemoved) { return (indexBeforeRemove >= size()) ? indexRemoved : indexBeforeRemove; } - @Override - public void forEach(BiConsumer action) { - checkNotNull(action); - for (int i = firstEntry; i != ENDPOINT; i = getSuccessor(i)) { - action.accept((K) keys[i], (V) values[i]); - } - } - @Override Set> createEntrySet() { @WeakOuter @@ -241,14 +232,6 @@ public T[] toArray(T[] a) { public Spliterator spliterator() { return Spliterators.spliterator(this, Spliterator.ORDERED | Spliterator.DISTINCT); } - - @Override - public void forEach(Consumer action) { - checkNotNull(action); - for (int i = firstEntry; i != ENDPOINT; i = getSuccessor(i)) { - action.accept((K) keys[i]); - } - } } return new KeySetImpl(); } @@ -271,14 +254,6 @@ public T[] toArray(T[] a) { public Spliterator spliterator() { return Spliterators.spliterator(this, Spliterator.ORDERED); } - - @Override - public void forEach(Consumer action) { - checkNotNull(action); - for (int i = firstEntry; i != ENDPOINT; i = getSuccessor(i)) { - action.accept((V) values[i]); - } - } } return new ValuesImpl(); } diff --git a/guava/src/com/google/common/collect/CompactLinkedHashSet.java b/guava/src/com/google/common/collect/CompactLinkedHashSet.java index 5734397aaf6a..b73397a8b217 100644 --- a/guava/src/com/google/common/collect/CompactLinkedHashSet.java +++ b/guava/src/com/google/common/collect/CompactLinkedHashSet.java @@ -16,15 +16,12 @@ package com.google.common.collect; -import static com.google.common.base.Preconditions.checkNotNull; - import com.google.common.annotations.GwtIncompatible; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Spliterator; import java.util.Spliterators; -import java.util.function.Consumer; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** @@ -53,7 +50,7 @@ class CompactLinkedHashSet extends CompactHashSet { /** Creates an empty {@code CompactLinkedHashSet} instance. */ public static CompactLinkedHashSet create() { - return new CompactLinkedHashSet(); + return new CompactLinkedHashSet<>(); } /** @@ -92,7 +89,7 @@ public static CompactLinkedHashSet create(E... elements) { * @throws IllegalArgumentException if {@code expectedSize} is negative */ public static CompactLinkedHashSet createWithExpectedSize(int expectedSize) { - return new CompactLinkedHashSet(expectedSize); + return new CompactLinkedHashSet<>(expectedSize); } private static final int ENDPOINT = -2; @@ -113,7 +110,10 @@ public static CompactLinkedHashSet createWithExpectedSize(int expectedSiz */ private transient int @MonotonicNonNull [] successor; + /** Pointer to the first node in the linked list, or {@code ENDPOINT} if there are no entries. */ private transient int firstEntry; + + /** Pointer to the last node in the linked list, or {@code ENDPOINT} if there are no entries. */ private transient int lastEntry; CompactLinkedHashSet() { @@ -125,10 +125,10 @@ public static CompactLinkedHashSet createWithExpectedSize(int expectedSiz } @Override - void init(int expectedSize, float loadFactor) { - super.init(expectedSize, loadFactor); - firstEntry = ENDPOINT; - lastEntry = ENDPOINT; + void init(int expectedSize) { + super.init(expectedSize); + this.firstEntry = ENDPOINT; + this.lastEntry = ENDPOINT; } @Override @@ -137,81 +137,74 @@ void allocArrays() { int expectedSize = elements.length; // allocated size may be different than initial capacity this.predecessor = new int[expectedSize]; this.successor = new int[expectedSize]; - Arrays.fill(predecessor, UNSET); Arrays.fill(successor, UNSET); } - private void succeeds(int pred, int succ) { + private int getPredecessor(int entry) { + return predecessor[entry]; + } + + @Override + int getSuccessor(int entry) { + return successor[entry]; + } + + private void setSuccessor(int entry, int succ) { + successor[entry] = succ; + } + + private void setPredecessor(int entry, int pred) { + predecessor[entry] = pred; + } + + private void setSucceeds(int pred, int succ) { if (pred == ENDPOINT) { firstEntry = succ; } else { - successor[pred] = succ; + setSuccessor(pred, succ); } if (succ == ENDPOINT) { lastEntry = pred; } else { - predecessor[succ] = pred; + setPredecessor(succ, pred); } } @Override void insertEntry(int entryIndex, E object, int hash) { super.insertEntry(entryIndex, object, hash); - succeeds(lastEntry, entryIndex); - succeeds(entryIndex, ENDPOINT); + setSucceeds(lastEntry, entryIndex); + setSucceeds(entryIndex, ENDPOINT); } @Override - void moveEntry(int dstIndex) { + void moveLastEntry(int dstIndex) { int srcIndex = size() - 1; - super.moveEntry(dstIndex); + super.moveLastEntry(dstIndex); - succeeds(predecessor[dstIndex], successor[dstIndex]); - if (srcIndex != dstIndex) { - succeeds(predecessor[srcIndex], dstIndex); - succeeds(dstIndex, successor[srcIndex]); + setSucceeds(getPredecessor(dstIndex), getSuccessor(dstIndex)); + if (dstIndex < srcIndex) { + setSucceeds(getPredecessor(srcIndex), dstIndex); + setSucceeds(dstIndex, getSuccessor(srcIndex)); } predecessor[srcIndex] = UNSET; successor[srcIndex] = UNSET; } - @Override - public void clear() { - if (needsAllocArrays()) { - return; - } - firstEntry = ENDPOINT; - lastEntry = ENDPOINT; - Arrays.fill(predecessor, 0, size(), UNSET); - Arrays.fill(successor, 0, size(), UNSET); - super.clear(); - } - @Override void resizeEntries(int newCapacity) { super.resizeEntries(newCapacity); int oldCapacity = predecessor.length; predecessor = Arrays.copyOf(predecessor, newCapacity); successor = Arrays.copyOf(successor, newCapacity); - if (oldCapacity < newCapacity) { Arrays.fill(predecessor, oldCapacity, newCapacity, UNSET); Arrays.fill(successor, oldCapacity, newCapacity, UNSET); } } - @Override - public Object[] toArray() { - return ObjectArrays.toArrayImpl(this); - } - - @Override - public T[] toArray(T[] a) { - return ObjectArrays.toArrayImpl(this, a); - } - @Override int firstEntryIndex() { return firstEntry; @@ -219,12 +212,17 @@ int firstEntryIndex() { @Override int adjustAfterRemove(int indexBeforeRemove, int indexRemoved) { - return (indexBeforeRemove == size()) ? indexRemoved : indexBeforeRemove; + return (indexBeforeRemove >= size()) ? indexRemoved : indexBeforeRemove; } @Override - int getSuccessor(int entryIndex) { - return successor[entryIndex]; + public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + + @Override + public T[] toArray(T[] a) { + return ObjectArrays.toArrayImpl(this, a); } @Override @@ -233,10 +231,14 @@ public Spliterator spliterator() { } @Override - public void forEach(Consumer action) { - checkNotNull(action); - for (int i = firstEntry; i != ENDPOINT; i = successor[i]) { - action.accept((E) elements[i]); + public void clear() { + if (needsAllocArrays()) { + return; } + this.firstEntry = ENDPOINT; + this.lastEntry = ENDPOINT; + Arrays.fill(predecessor, 0, size(), UNSET); + Arrays.fill(successor, 0, size(), UNSET); + super.clear(); } }