All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.carrotsearch.hppc.CharWormSet Maven / Gradle / Ivy

Go to download

High Performance Primitive Collections: data structures (maps, sets, lists, stacks, queues) generated for combinations of object and primitive types to conserve JVM memory and speed up execution.

There is a newer version: 0.10.0
Show newest version
  
package com.carrotsearch.hppc;

import com.carrotsearch.hppc.cursors.CharCursor;
import com.carrotsearch.hppc.predicates.CharPredicate;
import com.carrotsearch.hppc.procedures.CharProcedure;

import java.util.Arrays;
import java.util.Iterator;

import static com.carrotsearch.hppc.Containers.DEFAULT_EXPECTED_ELEMENTS;
import static com.carrotsearch.hppc.HashContainers.*;
import static com.carrotsearch.hppc.WormUtil.*;

/**
 * A hash set of chars, implemented using Worm Hashing strategy.
 *
 * 

This strategy is appropriate for a medium sized set (less than 2M keys). It takes more time * to put keys in the set because it maintains chains of keys having the same hash. Then the * lookup speed is fast even if the set is heavy loaded or hashes are clustered. On average it takes * slightly more memory than {@link CharHashSet}: heavier but the load factor is higher * (it varies around 80%) so it enlarges later.

* * @see HPPC interfaces diagram */ @com.carrotsearch.hppc.Generated( date = "2021-06-08T13:12:55+0200", value = "KTypeWormSet.java") public class CharWormSet extends AbstractCharCollection implements CharLookupContainer, CharSet, Preallocable, Cloneable, Accountable { /** * The array holding keys. */ public char [] keys; /** * {@code abs(next[i])=offset} to next chained entry index.

{@code next[i]=0} for free bucket.

The * offset is always forward, and the array is considered circular, meaning that an entry at the end of the * array may point to an entry at the beginning with a positive offset.

The offset is always forward, but the * sign of the offset encodes head/tail of chain. {@link #next}[i] > 0 for the first head-of-chain entry (within * [1,{@link WormUtil#maxOffset}]), {@link #next}[i] < 0 for the subsequent tail-of-chain entries (within [-{@link * WormUtil#maxOffset},-1]. For the last entry in the chain, {@code abs(next[i])=}{@link WormUtil#END_OF_CHAIN}.

*/ public byte[] next; /** * Set size (number of entries). */ protected int size; /** * Seed used to ensure the hash iteration order is different from an iteration to another. */ protected int iterationSeed; /** * New instance with sane defaults. */ public CharWormSet() { this(DEFAULT_EXPECTED_ELEMENTS); } /** * New instance with the provided defaults. * *

There is no load factor parameter as this set enlarges automatically. In practice the load factor * varies around 80% (between 75% and 90%). The load factor is 100% for tiny sets.

* * @param expectedElements The expected number of elements. The capacity of the set is calculated based on it. */ public CharWormSet(int expectedElements) { if (expectedElements < 0) { throw new IllegalArgumentException("Invalid expectedElements=" + expectedElements); } iterationSeed = HashContainers.nextIterationSeed(); ensureCapacity(expectedElements); } /** * Creates a new instance from all elements of another container. */ public CharWormSet(CharContainer container) { this(container.size()); addAll(container); } /** * Create a set from a variable number of arguments or an array of * char. The elements are copied from the argument to the * internal buffer. */ /* */ public static CharWormSet from(char... elements) { CharWormSet set = new CharWormSet(elements.length); set.addAll(elements); return set; } /** * Clones this set. The cloning operation is efficient because it copies directly the internal arrays, without * having to put elements in the cloned set. The cloned set has the same elements and the same capacity as this set. * * @return A shallow copy of this set. */ @Override public CharWormSet clone() { try { /* */ CharWormSet cloneSet = (CharWormSet) super.clone(); cloneSet.keys = keys.clone(); cloneSet.next = next.clone(); cloneSet.iterationSeed = HashContainers.nextIterationSeed(); return cloneSet; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } /** {@inheritDoc} */ @Override public int size() { return size; } /** {@inheritDoc} */ @Override public boolean isEmpty() { return size == 0; } /** {@inheritDoc} */ @Override public boolean contains(char key) { // Compute the key hash index. int hashIndex = hashMod(key); int nextOffset = next[hashIndex]; if (nextOffset <= 0) { // The bucket is either free, or only used for chaining, so no entry for the key. return false; } // The bucket contains a head-of-chain entry. // Look for the key in the chain. return searchInChain(key, hashIndex, nextOffset) >= 0; } /** {@inheritDoc} */ @Override public boolean add(char key) { return add(key, false, true); } /** * Adds all elements from the given list (vararg) to this set. * * @return Returns the number of elements actually added as a result of this * call (not previously present in the set). */ /* */ public final int addAll(char... elements) { ensureCapacity(elements.length); int count = 0; for (char e : elements) { if (add(e)) { count++; } } return count; } /** * Adds all elements from the given {@link CharContainer} to this set. * * @return Returns the number of elements actually added as a result of this * call (not previously present in the set). */ public int addAll(CharContainer container) { ensureCapacity(container.size()); return addAll((Iterable) container); } /** * Adds all elements from the given iterable to this set. * * @return Returns the number of elements actually added as a result of this * call (not previously present in the set). */ public int addAll(Iterable iterable) { int count = 0; for (CharCursor cursor : iterable) { if (add(cursor.value)) { count++; } } return count; } /** * An alias for the (preferred) {@link #removeAll}. */ public boolean remove(char key) { final byte[] next = this.next; // Compute the key hash index. int hashIndex = hashMod(key); int nextOffset = next[hashIndex]; if (nextOffset <= 0) { // The bucket is either free, or in tail-of-chain, so no entry for the key. return false; } // The bucket contains a head-of-chain entry. // Look for the key in the chain. int previousEntryIndex = searchInChainReturnPrevious(key, hashIndex, nextOffset); if (previousEntryIndex < 0) { // No entry matches the key. return false; } int entryToRemoveIndex = previousEntryIndex == Integer.MAX_VALUE ? hashIndex : addOffset(previousEntryIndex, Math.abs(next[previousEntryIndex]), next.length); remove(entryToRemoveIndex, previousEntryIndex); return true; } /** {@inheritDoc} */ @Override public int removeAll(char key) { return remove(key) ? 1 : 0; } /** * Removes all keys present in a given container. * * @return Returns the number of elements actually removed as a result of this call. */ public int removeAll(CharContainer other) { // Try to iterate over the smaller set or over the container that isn't implementing // efficient contains() lookup. int size = size(); if (other.size() >= size && other instanceof CharLookupContainer) { final char[] keys = this.keys; final byte[] next = this.next; final int capacity = next.length; int entryIndex = 0; while (entryIndex < capacity) { char key; if (next[entryIndex] != 0 && other.contains(key = keys[entryIndex])) { this.remove(key); } else { entryIndex++; } } } else { for (CharCursor c : other) { remove( c.value); } } return size - size(); } /** {@inheritDoc} */ @Override public int removeAll(CharPredicate predicate) { final char[] keys = this.keys; final byte[] next = this.next; final int capacity = next.length; int size = size(); int entryIndex = 0; while (entryIndex < capacity) { char key; if (next[entryIndex] != 0 && predicate.apply(key = keys[entryIndex])) { this.remove(key); } else { entryIndex++; } } return size - size(); } /** {@inheritDoc} */ @Override public T forEach(T procedure) { final char[] keys = this.keys; final byte[] next = this.next; int seed = nextIterationSeed(); int inc = iterationIncrement(seed); for (int i = 0, mask = next.length - 1, slot = seed & mask; i <= mask; i++, slot = (slot + inc) & mask) { if (next[slot] != 0) { procedure.apply(keys[slot]); } } return procedure; } /** {@inheritDoc} */ @Override public T forEach(T predicate) { final char[] keys = this.keys; final byte[] next = this.next; int seed = nextIterationSeed(); int inc = iterationIncrement(seed); for (int i = 0, mask = next.length - 1, slot = seed & mask; i <= mask; i++, slot = (slot + inc) & mask) { if (next[slot] != 0) { if (!predicate.apply(keys[slot])) { break; } } } return predicate; } /** {@inheritDoc} */ @Override public Iterator iterator() { return new EntryIterator(); } /** {@inheritDoc} */ @Override public void clear() { Arrays.fill(next, (byte) 0); size = 0; /* */ } /** {@inheritDoc} */ @Override public void release() { keys = null; next = null; size = 0; ensureCapacity(DEFAULT_EXPECTED_ELEMENTS); } /** {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } CharSet set = (CharSet) o; final int size = this.size; if (size != set.size()) { return false; } final char[] keys = this.keys; final byte[] next = this.next; // Iterate all entries. for (int index = 0, entryCount = 0; entryCount < size; index++) { if (next[index] != 0) { if (!set.contains(keys[index])) { return false; } entryCount++; } } return true; } /** {@inheritDoc} */ @Override public int hashCode() { int hashCode = 0; // Iterate all entries. final int size = this.size; for (int index = 0, entryCount = 0; entryCount < size; index++) { if (next[index] != 0) { hashCode += BitMixer.mixPhi(keys[index]); entryCount++; } } return hashCode; } protected int hashKey(char key) { return BitMixer.mixPhi(key); } private int hashMod(char key) { return hashKey(key) & (next.length - 1); } /** * Returns a logical "index" of a given key that can be used to speed up * follow-up logic in certain scenarios (conditional logic). * * The semantics of "indexes" are not strictly defined. Indexes may * (and typically won't be) contiguous. * * The index is valid only between modifications (it will not be affected * by read-only operations). * * @see #indexExists * @see #indexGet * @see #indexInsert * @see #indexReplace * * @param key * The key to locate in the set. * @return A non-negative value of the logical "index" of the key in the set * or a negative value if the key did not exist. */ public int indexOf(char key) { int hashIndex = hashMod(key); int nextOffset = next[hashIndex]; if (nextOffset <= 0) { return ~hashIndex; } return searchInChain(key, hashIndex, nextOffset); } /** * @see #indexOf * * @param index The index of a given key, as returned from {@link #indexOf}. * @return Returns true if the index corresponds to an existing key * or false otherwise. This is equivalent to checking whether the index is * a positive value (existing keys) or a negative value (non-existing keys). */ public boolean indexExists(int index) { assert index < next.length; return index >= 0; } /** * Returns the exact value of the existing key. This method makes sense for sets * of objects which define custom key-equality relationship. * * @see #indexOf * * @param index The index of an existing key. * @return Returns the equivalent key currently stored in the set. * @throws AssertionError If assertions are enabled and the index does * not correspond to an existing key. */ public char indexGet(int index) { assert checkIndex(index, next.length); assert next[index] != 0; return keys[index]; } /** * Replaces the existing equivalent key with the given one and returns any previous value * stored for that key. * * @see #indexOf * * @param index The index of an existing key. * @param equivalentKey The key to put in the set as a replacement. Must be equivalent to * the key currently stored at the provided index. * @return Returns the previous key stored in the set. * @throws AssertionError If assertions are enabled and the index does * not correspond to an existing key. */ public char indexReplace(int index, char equivalentKey) { assert checkIndex(index, next.length); assert next[index] != 0; assert ((equivalentKey) == ( keys[index])); char previousKey = keys[index]; keys[index] = equivalentKey; return previousKey; } /** * Inserts a key for an index that is not present in the set. This method * may help in avoiding double recalculation of the key's hash. * * @see #indexOf * * @param index The index of a previously non-existing key, as returned from * {@link #indexOf}. * @throws AssertionError If assertions are enabled and the index does * not correspond to an existing key. */ public void indexInsert(int index, char key) { assert index < 0 : "The index must not point at an existing key."; index = ~index; if (next[index] == 0) { keys[index] = key; next[index] = END_OF_CHAIN; size++; } else { add(key, true, true); } } /** * Removes a key at an index previously acquired from {@link #indexOf}. * * @see #indexOf * * @param index The index of the key to remove, as returned from {@link #indexOf}. * @throws AssertionError If assertions are enabled and the index does * not correspond to an existing key. */ public void indexRemove(int index) { assert checkIndex(index, next.length); assert next[index] != 0; remove(index, Integer.MAX_VALUE); } /** {@inheritDoc} */ @Override public String toString() { StringBuilder sBuilder = new StringBuilder(); sBuilder.append('['); // Iterate all entries. for (int index = 0, entryCount = 0; entryCount < size; index++) { if (next[index] != 0) { if (entryCount > 0) { sBuilder.append(", "); } sBuilder.append(keys[index]); entryCount++; } } sBuilder.append(']'); return sBuilder.toString(); } /** {@inheritDoc} */ @Override public void ensureCapacity(int expectedElements) { allocateBuffers((int) (expectedElements / FIT_LOAD_FACTOR)); } /** {@inheritDoc} */ @Override public String visualizeKeyDistribution(int characters) { return CharBufferVisualizer.visualizeKeyDistribution(keys, next.length - 1, characters); } /** {@inheritDoc} */ @Override public long ramBytesAllocated() { // int: size, iterationSeed return RamUsageEstimator.NUM_BYTES_OBJECT_HEADER + Integer.BYTES * 2 + RamUsageEstimator.shallowSizeOfArray(keys) + RamUsageEstimator.shallowSizeOfArray(next); } /** {@inheritDoc} */ @Override public long ramBytesUsed() { // int: size, iterationSeed return RamUsageEstimator.NUM_BYTES_OBJECT_HEADER + Integer.BYTES * 2 + RamUsageEstimator.shallowUsedSizeOfArray(keys, size()) + RamUsageEstimator.shallowUsedSizeOfArray(next, size()); } protected void allocateBuffers(int capacity) { capacity = Math.max(capacity, size); capacity = Math.max(BitUtil.nextHighestPowerOfTwo(capacity), MIN_HASH_ARRAY_LENGTH); if (capacity > MAX_HASH_ARRAY_LENGTH) { throw new BufferAllocationException("Maximum array size exceeded (capacity: %d)", capacity); } if (keys != null && keys.length == capacity) { return; } char[] oldKeys = keys; byte[] oldNext = next; keys = (new char [capacity]); next = new byte[capacity]; if (oldKeys != null) { putOldEntries(oldKeys, oldNext, size); } } /** * Puts old entries after enlarging this set. Old entries are guaranteed not to be already contained by this set. *

This method does not modify this set {@link #size}. It may enlarge this set if it needs room to put the entry.

* * @param oldKeys The old keys. * @param oldNext The old next offsets. * @param entryNum The number of non null old entries. It is supported to set a value larger than the real count. */ private void putOldEntries(char[] oldKeys, byte[] oldNext, int entryNum) { int entryCount = 0; // Iterate new entries. // The condition on index < endIndex is required because the putNewEntry() call below may need to // enlarge the set, which calls this method again. And in this case entryNum is larger than the real number. for (int index = 0, endIndex = oldNext.length; entryCount < entryNum && index < endIndex; index++) { if (oldNext[index] != 0) { // Compute the key hash index. char oldKey = oldKeys[index]; int hashIndex = hashMod(oldKey); putNewEntry(hashIndex, next[hashIndex], oldKey); entryCount++; } } } /** * Adds an element in this set. * * @param newGuaranteed Whether the element is guaranteed to not be already present. * @param sizeIncrease Whether to increment {@link #size}. * @return {@code true} if the element has been added; {@code false} otherwise. */ private boolean add(char key, boolean newGuaranteed, boolean sizeIncrease) { // Compute the key hash index. int hashIndex = hashMod(key); int nextOffset = next[hashIndex]; boolean added = false; if (nextOffset > 0 && !newGuaranteed) { // The bucket contains a head-of-chain entry. // Look for the key in the chain. int entryIndex = searchInChain(key, hashIndex, nextOffset); if (entryIndex >= 0) { // An entry in the chain matches the key. Do not replace the existing element. return false; } if (enlargeIfNeeded()) { hashIndex = hashMod(key); nextOffset = next[hashIndex]; } else { // No entry matches the key. Append the new entry at the tail of the chain. // ~entryIndex is the index of the last entry in the chain. if (!appendTailOfChain(~entryIndex, key)) { // No free bucket in the range. Enlarge the set and put again. enlargeAndPutNewEntry(key); } added = true; } } else if (enlargeIfNeeded()) { hashIndex = hashMod(key); nextOffset = next[hashIndex]; } if (!added) { // No entry matches the key. Add the new entry. putNewEntry(hashIndex, nextOffset, key); } if (sizeIncrease) { size++; } return true; } private boolean enlargeIfNeeded() { if (size >= next.length) { allocateBuffers(next.length << 1); return true; } return false; } private void enlargeAndPutNewEntry(char key) { allocateBuffers(next.length << 1); add(key, true, false); } /** * Removes the entry at the specified removal index. * Decrements {@link #size}. * * @param entryToRemoveIndex The index of the entry to remove. * @param previousEntryIndex The index of the entry in the chain preceding the entry to remove; or * {@link Integer#MAX_VALUE} if unknown or if the entry to remove is the head-of-chain. */ private void remove(int entryToRemoveIndex, int previousEntryIndex) { assert checkIndex(entryToRemoveIndex, next.length); assert previousEntryIndex == Integer.MAX_VALUE || checkIndex(previousEntryIndex, next.length); final byte[] next = this.next; // Find the last entry of the chain. // Replace the removed entry by the last entry of the chain. int nextOffset = next[entryToRemoveIndex]; int beforeLastIndex = findLastOfChain(entryToRemoveIndex, nextOffset, true, next); int lastIndex; if (beforeLastIndex == -1) { // The entry to remove is the last of the chain. lastIndex = entryToRemoveIndex; if (nextOffset < 0) { // Removing the last entry in a chain of at least two entries. beforeLastIndex = previousEntryIndex == Integer.MAX_VALUE ? findPreviousInChain(entryToRemoveIndex, next) : previousEntryIndex; // Unlink the last entry which replaces the removed entry. next[beforeLastIndex] = (byte) (next[beforeLastIndex] > 0 ? END_OF_CHAIN : -END_OF_CHAIN); } } else { int beforeLastNextOffset = next[beforeLastIndex]; lastIndex = addOffset(beforeLastIndex, Math.abs(beforeLastNextOffset), next.length); assert entryToRemoveIndex != lastIndex; // The entry to remove is before the last of the chain. Replace it by the last one. keys[entryToRemoveIndex] = keys[lastIndex]; // Unlink the last entry which replaces the removed entry. next[beforeLastIndex] = (byte) (beforeLastNextOffset > 0 ? END_OF_CHAIN : -END_OF_CHAIN); } // Free the last entry of the chain. keys[lastIndex] = ((char) 0); next[lastIndex] = 0; size--; } /** * Appends a new entry at the tail of an entry chain. * * @param lastEntryIndex The index of the last entry in the chain. * @return true if the new entry is added successfully; false if there is no free bucket * in the range (so this set needs to be enlarged to make room). */ private boolean appendTailOfChain(int lastEntryIndex, char key) { return appendTailOfChain(lastEntryIndex, key, ExcludedIndexes.NONE, 0); } /** * Appends a new entry at the tail of an entry chain. * * @param lastEntryIndex The index of the last entry in the chain. * @param excludedIndexes Indexes to exclude from the search. * @param recursiveCallLevel Keeps track of the recursive call level (starts at 0). * @return true if the new entry is added successfully; false if there is no free bucket * in the range (so this set needs to be enlarged to make room). */ private boolean appendTailOfChain(int lastEntryIndex, char key, ExcludedIndexes excludedIndexes, int recursiveCallLevel) { // Find the next free bucket by linear probing. final int capacity = next.length; int searchFromIndex = addOffset(lastEntryIndex, 1, capacity); int freeIndex = searchFreeBucket(searchFromIndex, maxOffset(capacity), -1, next); if (freeIndex == -1) { freeIndex = searchAndMoveBucket(searchFromIndex, maxOffset(capacity), excludedIndexes, recursiveCallLevel); if (freeIndex == -1) return false; } keys[freeIndex] = key; next[freeIndex] = -END_OF_CHAIN; int nextOffset = getOffsetBetweenIndexes(lastEntryIndex, freeIndex, next.length); next[lastEntryIndex] = (byte) (next[lastEntryIndex] > 0 ? nextOffset : -nextOffset); // Keep the offset sign. return true; } /** * Searches a movable tail-of-chain bucket by linear probing to the right. If a movable tail-of-chain is found, this * method attempts to move it. * * @param fromIndex The index of the entry to start searching from. * @param range The maximum number of buckets to search, starting from index (included), up to index + * range (excluded). * @param excludedIndexes Indexes to exclude from the search. * @param recursiveCallLevel Keeps track of the recursive call level (starts at 0). * @return The index of the freed bucket; or -1 if no bucket could be freed within the range. */ private int searchAndMoveBucket(int fromIndex, int range, ExcludedIndexes excludedIndexes, int recursiveCallLevel) { assert checkIndex(fromIndex, next.length); assert range >= 0 && range <= maxOffset(next.length) : "range=" + range + ", maxOffset=" + maxOffset(next.length); int remainingAttempts = RECURSIVE_MOVE_ATTEMPTS[recursiveCallLevel]; if (remainingAttempts <= 0 || range <= 0) { return -1; } final byte[] next = this.next; final int capacity = next.length; int nextRecursiveCallLevel = recursiveCallLevel + 1; for (int index = fromIndex + range - 1; index >= fromIndex; index--) { int rolledIndex = index & (capacity - 1); if (excludedIndexes.isIndexExcluded(rolledIndex)) { continue; } int nextOffset = next[rolledIndex]; if (nextOffset < 0) { // Attempt to move the tail of chain. if (moveTailOfChain(rolledIndex, nextOffset, excludedIndexes, nextRecursiveCallLevel)) return rolledIndex; if (--remainingAttempts <= 0) return -1; } } return -1; } /** * Puts a new entry that is guaranteed not to be already contained by this set.

This method does not modify this * set {@link #size}. It may enlarge this set if it needs room to put the entry.

* * @param hashIndex The hash index where to put the entry (= {@link #hashMod}(key)). * @param nextOffset The current value of {@link #next}[hashIndex]. */ private void putNewEntry(int hashIndex, int nextOffset, char key) { assert hashIndex == hashMod(key) : "hashIndex=" + hashIndex + ", hashReduce(key)=" + hashMod(key); assert checkIndex(hashIndex, next.length); assert Math.abs(nextOffset) <= END_OF_CHAIN : "nextOffset=" + nextOffset; assert nextOffset == next[hashIndex] : "nextOffset=" + nextOffset + ", next[hashIndex]=" + next[hashIndex]; if (nextOffset > 0) { // The bucket contains a head-of-chain entry. // Append the new entry at the chain tail, after the last entry of the chain. If there is no free bucket in // the range, enlarge this set and put the new entry. if (!appendTailOfChain(findLastOfChain(hashIndex, nextOffset, false, next), key)) { enlargeAndPutNewEntry(key); } } else { if (nextOffset < 0) { // Bucket at hash index contains a movable tail-of-chain entry. Move it to free the bucket. if (!moveTailOfChain(hashIndex, nextOffset, ExcludedIndexes.NONE, 0)) { // No free bucket in the range. Enlarge the set and put again. enlargeAndPutNewEntry(key); return; } } // Bucket at hash index is free. Add the new head-of-chain entry. keys[hashIndex] = key; next[hashIndex] = END_OF_CHAIN; } } /** * Moves a tail-of-chain entry to another free bucket. * * @param tailIndex The index of the tail-of-chain entry. * @param nextOffset The value of {@link #next}[tailIndex]. It is always < 0. * @param excludedIndexes Indexes to exclude from the search. * @param recursiveCallLevel Keeps track of the recursive call level (starts at 0). * @return Whether the entry has been successfully moved; or if it could not because there is no free bucket in the * range. */ private boolean moveTailOfChain(int tailIndex, int nextOffset, ExcludedIndexes excludedIndexes, int recursiveCallLevel) { assert checkIndex(tailIndex, next.length); assert nextOffset < 0 && nextOffset >= -END_OF_CHAIN : "nextOffset=" + nextOffset; assert nextOffset == next[tailIndex] : "nextOffset=" + nextOffset + ", next[tailIndex]=" + next[tailIndex]; // Find the next free bucket by linear probing. // It must be within a range of maxOffset of the previous entry in the chain, // and not beyond the next entry in the chain. final byte[] next = this.next; final int capacity = next.length; final int maxOffset = maxOffset(capacity); int previousIndex = findPreviousInChain(tailIndex, next); int absPreviousOffset = Math.abs(next[previousIndex]); int nextIndex = nextOffset == -END_OF_CHAIN ? -1 : addOffset(tailIndex, -nextOffset, capacity); int offsetFromPreviousToNext = absPreviousOffset - nextOffset; int searchFromIndex; int searchRange; boolean nextIndexWithinRange; // Compare [the offset from previous entry to next entry] to [maxOffset]. if (offsetFromPreviousToNext <= maxOffset) { // The next entry in the chain is inside the maximum offset range. // Prepare to search for a free bucket starting from the tail-of-chain entry, up to the next entry in the chain. searchFromIndex = addOffset(previousIndex, 1, capacity); searchRange = offsetFromPreviousToNext - 1; nextIndexWithinRange = true; } else { // The next entry is not inside the maximum range. It is always the case if nextOffset is -END_OF_CHAIN. // Prepare to search for a free bucket starting from the tail-of-chain entry, up to maxOffset from the // previous entry. if (nextIndex == -1) { searchFromIndex = addOffset(previousIndex, 1, capacity); searchRange = maxOffset; } else { searchFromIndex = addOffset(nextIndex, -maxOffset, capacity); int searchToIndex = addOffset(previousIndex, maxOffset, capacity); searchRange = getOffsetBetweenIndexes(searchFromIndex, searchToIndex, capacity) + 1; } nextIndexWithinRange = false; } int freeIndex = searchFreeBucket(searchFromIndex, searchRange, tailIndex, next); if (freeIndex == -1) { // No free bucket in the range. if (nextIndexWithinRange && appendTailOfChain( findLastOfChain(nextIndex, next[nextIndex], false, next), keys[tailIndex], excludedIndexes, recursiveCallLevel)) { // The entry to move has been appended to the tail of the chain. // Complete the move by linking the previous entry to the next entry (which is within range). int previousOffset = getOffsetBetweenIndexes(previousIndex, nextIndex, capacity); next[previousIndex] = (byte) (next[previousIndex] > 0 ? previousOffset : -previousOffset); // Keep the offset sign. return true; } else { ExcludedIndexes recursiveExcludedIndexes = excludedIndexes.union(ExcludedIndexes.fromChain(previousIndex, next)); if ((freeIndex = searchAndMoveBucket(searchFromIndex, searchRange, recursiveExcludedIndexes, recursiveCallLevel)) == -1) { // No free bucket after the tail of the chain, and no movable entry. No bucket available around. // The move fails (and this set will be enlarged by the calling method). return false; } } } // Move the entry to the free index. // No need to set keys[tailIndex] to null here because they will be set when this method returns, // or the set will be enlarged and rehashed. keys[freeIndex] = keys[tailIndex]; next[freeIndex] = (byte) (nextOffset == -END_OF_CHAIN ? nextOffset : -getOffsetBetweenIndexes(freeIndex, nextIndex, capacity)); int previousOffset = getOffsetBetweenIndexes(previousIndex, freeIndex, capacity); next[previousIndex] = (byte) (next[previousIndex] > 0 ? previousOffset : -previousOffset); // Keep the offset sign. assert next[freeIndex] < 0 : "freeIndex=" + freeIndex + ", next[freeIndex]=" + next[freeIndex]; return true; } /** * Searches an entry in a chain. * * @param key The searched entry key. * @param index The head-of-chain index. * @param nextOffset next[index]. It must be > 0. * @return The matched entry index; or 2's complement ~index if not found, index of the last entry in the chain. */ private int searchInChain(char key, int index, int nextOffset) { assert checkIndex(index, next.length); assert nextOffset > 0 && nextOffset <= END_OF_CHAIN : "nextOffset=" + nextOffset; assert nextOffset == next[index] : "nextOffset=" + nextOffset + ", next[index]=" + next[index]; // There is at least one entry at this bucket. Check the first head-of-chain. if (((key) == (keys[index]))) { // The first head-of-chain entry matches the key. Return its index. return index; } // Follow the entry chain for this bucket. final int capacity = next.length; while (nextOffset != END_OF_CHAIN) { index = addOffset(index, nextOffset, capacity); // Jump forward. if (((key) == (keys[index]))) { // An entry in the chain matches the key. Return its index. return index; } nextOffset = -next[index]; // Next offsets are negative for tail-of-chain entries. assert nextOffset > 0 : "nextOffset=" + nextOffset; } // No entry matches the key. Return the last entry index as 2's complement. return ~index; } /** * Searches an entry in a chain and returns its previous entry in the chain. * * @param key The searched entry key. * @param index The head-of-chain index. * @param nextOffset next[index]. It must be > 0. * @return The index of the entry preceding the matched entry; or {@link Integer#MAX_VALUE} if the head-of-chain * matches; or 2's complement ~index if not found, index of the last entry in the chain. */ private int searchInChainReturnPrevious(char key, int index, int nextOffset) { assert checkIndex(index, next.length); assert nextOffset > 0 && nextOffset <= END_OF_CHAIN : "nextOffset=" + nextOffset; assert nextOffset == next[index] : "nextOffset=" + nextOffset + ", next[index]=" + next[index]; // There is at least one entry at this bucket. Check the first head-of-chain. if (((key) == (keys[index]))) { // The first head-of-chain entry matches the key. Return Integer.MAX_VALUE as there is no previous entry. return Integer.MAX_VALUE; } // Follow the entry chain for this bucket. final int capacity = next.length; while (nextOffset != END_OF_CHAIN) { int previousIndex = index; index = addOffset(index, nextOffset, capacity); // Jump forward. if (((key) == (keys[index]))) { // An entry in the chain matches the key. Return the previous entry index. return previousIndex; } nextOffset = -next[index]; // Next offsets are negative for tail-of-chain entries. assert nextOffset > 0 : "nextOffset=" + nextOffset; } // No entry matches the key. Return the last entry index as 2's complement. return ~index; } /** * Provides the next iteration seed used to build the iteration starting slot and offset increment. * This method does not need to be synchronized, what matters is that each thread gets a sequence of varying seeds. */ protected int nextIterationSeed() { return iterationSeed = BitMixer.mixPhi(iterationSeed); } /** * An iterator over the elements. */ private class EntryIterator extends AbstractIterator { private final CharCursor cursor; private final int increment; private int index; private int slot; public EntryIterator() { cursor = new CharCursor(); int seed = nextIterationSeed(); increment = iterationIncrement(seed); slot = seed & (next.length - 1); } @Override protected CharCursor fetch() { final int mask = next.length - 1; while (index <= mask) { index++; slot = (slot + increment) & mask; if (next[slot] != 0) { cursor.index = slot; cursor.value = keys[slot]; return cursor; } } return done(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy