/** * Allocate internal buffers for a given capacity. * * @param capacity New capacity (must be a power of two). */ @SuppressWarnings("boxing") private void allocateBuffers(final int capacity) { try { final KType[] keys = Intrinsics.<KType>newArray(capacity); /*! #if ($RH) !*/ final int[] allocated = new int[capacity]; /*! #end !*/ this.keys = keys; /*! #if ($RH) !*/ this.hash_cache = allocated; /*! #end !*/ // allocate so that there is at least one slot that remains allocated = false // this is compulsory to guarantee proper stop in searching loops this.resizeAt = HashContainers.expandAtCount(capacity, this.loadFactor); } catch (final OutOfMemoryError e) { throw new BufferAllocationException( "Not enough memory to allocate buffers to grow from %d -> %d elements", e, (this.keys == null) ? 0 : this.keys.length, capacity); } }
/** * Expand the internal storage buffers (capacity) or rehash current keys and values if there are a * lot of deleted slots. */ private void expandAndAdd(final KType pendingKey, final int freeSlot) { assert this.assigned == this.resizeAt; // default sentinel value is never in the keys[] array, so never trigger reallocs assert (!Intrinsics.<KType>isEmpty(pendingKey)); // Try to allocate new buffers first. If we OOM, it'll be now without // leaving the data structure in an inconsistent state. final KType[] oldKeys = Intrinsics.<KType[]>cast(this.keys); allocateBuffers( HashContainers.nextBufferSize(this.keys.length, this.assigned, this.loadFactor)); // We have succeeded at allocating new data so insert the pending key/value at // the free slot in the old arrays before rehashing. this.assigned++; oldKeys[freeSlot] = pendingKey; // Variables for adding final int mask = this.keys.length - 1; KType key = Intrinsics.<KType>empty(); // adding phase int slot = -1; final KType[] keys = Intrinsics.<KType[]>cast(this.keys); /*! #if ($RH) !*/ final int[] cached = this.hash_cache; /*! #end !*/ /*! #if ($RH) !*/ KType tmpKey = Intrinsics.<KType>empty(); int tmpAllocated = -1; int initial_slot = -1; int dist = -1; int existing_distance = -1; /*! #end !*/ // iterate all the old arrays to add in the newly allocated buffers // It is important to iterate backwards to minimize the conflict chain length ! final int perturb = this.perturbation; for (int i = oldKeys.length; --i >= 0; ) { // only consider non-empty slots, of course if (!Intrinsics.<KType>isEmpty(key = oldKeys[i])) { slot = REHASH2(key, perturb) & mask; /*! #if ($RH) !*/ initial_slot = slot; dist = 0; /*! #end !*/ // similar to add(), except all inserted keys are known to be unique. while (is_allocated(slot, keys)) { /*! #if ($RH) !*/ // re-shuffle keys to minimize variance existing_distance = probe_distance(slot, cached); if (dist > existing_distance) { // swap current (key, value, initial_slot) with slot places tmpKey = keys[slot]; keys[slot] = key; key = tmpKey; tmpAllocated = cached[slot]; cached[slot] = initial_slot; initial_slot = tmpAllocated; /*! #if($DEBUG) !*/ // Check invariants assert cached[slot] == (REHASH(keys[slot]) & mask); assert initial_slot == (REHASH(key) & mask); /*! #end !*/ dist = existing_distance; } // endif /*! #end !*/ slot = (slot + 1) & mask; /*! #if ($RH) !*/ dist++; /*! #end !*/ } // end while // place it at that position /*! #if ($RH) !*/ cached[slot] = initial_slot; /*! #end !*/ keys[slot] = key; /*! #if ($RH) !*/ /*! #if($DEBUG) !*/ // Check invariants assert cached[slot] == (REHASH(keys[slot]) & mask); /*! #end !*/ /*! #end !*/ } } }
/** Creates a hash set with the given capacity and load factor. */ public KTypeHashSet(final int initialCapacity, final double loadFactor) { this.loadFactor = loadFactor; // take into account of the load factor to guarantee no reallocations before reaching // initialCapacity. allocateBuffers(HashContainers.minBufferSize(initialCapacity, loadFactor)); }