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

com.hazelcast.internal.util.collection.OAHashSet Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hazelcast.internal.util.collection;

import com.hazelcast.internal.util.QuickMath;

import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;

import static com.hazelcast.internal.nio.Bits.FLOAT_SIZE_IN_BYTES;
import static com.hazelcast.internal.nio.Bits.INT_SIZE_IN_BYTES;
import static com.hazelcast.internal.util.JVMUtil.REFERENCE_COST_IN_BYTES;
import static com.hazelcast.internal.util.Preconditions.checkNotNull;

/**
 * Not thread-safe open-addressing hash {@link Set} implementation with linear
 * probing for CPU cache efficiency. This implementation caches the hashes
 * of the elements stored in the set. This caching enables avoiding
 * expensive {@link #hashCode()} calls when rehashing at the cost of the
 * increased memory consumption.
 * 

* Besides avoiding {@link #hashCode()} calls on rehashing, this * implementation offers methods that accept the hash together with the * element if it is already known on the caller side. * See {@link #add(Object, int)}, {@link #contains(Object, int)}, {@link #remove(Object, int)}. *

* This {@link Set} implementation does not permit null elements. *

* This {@link Set} implementation does not permit concurrent modifications * during iteration. *

* Please note that this {@link Set} implementation does not shrink when * elements are removed. * * @param The type of the elements stored in the set */ public class OAHashSet extends AbstractSet { private static final int DEFAULT_INITIAL_CAPACITY = 16; private static final float DEFAULT_LOAD_FACTOR = 0.6F; private final float loadFactor; private int[] hashes; private Object[] table; private int resizeThreshold; private int capacity; private int mask; private int size; /** * The version of this set. Used to detect concurrent modification when * iterating over the elements in the set with {@link ElementIterator} */ private int version; /** * Constructs an {@link OAHashSet} instance with default initial * capacity and default load factor * * @see #DEFAULT_INITIAL_CAPACITY * @see #DEFAULT_LOAD_FACTOR */ public OAHashSet() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } /** * Constructs an {@link OAHashSet} instance with the specified * initial capacity and with the default load factor * * @param initialCapacity the initial capacity of the set to be created * @see #DEFAULT_LOAD_FACTOR */ public OAHashSet(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * Constructs an {@link OAHashSet} instance with the specified * initial capacity and load factor * * @param initialCapacity the initial capacity of the set to be created * @param loadFactor the load factor of the set to be created */ public OAHashSet(int initialCapacity, float loadFactor) { // the parameter checks below are intentionally not done via Preconditions // the error messages provided to the preconditions are created unconditionally // which creates plenty StringBuilders and for building the error message // if many instances are created in a loop this increases the GC pressure significantly if (initialCapacity < 0) { throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); } if (loadFactor <= 0 || loadFactor >= 1 || Float.isNaN(loadFactor)) { throw new IllegalArgumentException("Illegal load factor: " + loadFactor); } this.capacity = QuickMath.nextPowerOfTwo(initialCapacity); this.loadFactor = loadFactor; this.resizeThreshold = (int) (capacity * loadFactor); this.mask = capacity - 1; this.hashes = new int[capacity]; this.table = new Object[capacity]; } @Override public boolean add(E element) { return add(element, element.hashCode()); } /** * Adds the specified element to this set if it is not already present. *

* This variant of {@link #add(Object)} acts as an optimisation to * enable avoiding {@link #hashCode()} calls if the hash is already * known on the caller side. * * @param elementToAdd element to be added to this set * @param hash the hash of the element to be added * @return true if this set did not already contain the specified * element * @see #add(Object) */ public boolean add(E elementToAdd, int hash) { checkNotNull(elementToAdd); int index = hash & mask; // using the hashes array for looping and comparison if possible, hence we're cache friendly while (hashes[index] != 0 || table[index] != null) { if (hash == hashes[index] && elementToAdd.equals(table[index])) { return false; } index = ++index & mask; } size++; version++; table[index] = elementToAdd; hashes[index] = hash; if (size > resizeThreshold) { increaseCapacity(); } return true; } @Override public boolean contains(Object objectToCheck) { return contains(objectToCheck, objectToCheck.hashCode()); } /** * Returns true if this set contains the specified element * with the hash provided in parameter. *

* This variant of {@link #contains(Object)} acts as an optimisation to * enable avoiding {@link #hashCode()} calls if the hash is already * known on the caller side. * * @param objectToCheck element whose presence in this set is to be tested * @param hash the hash of the element to be tested * @return true if this set contains the specified element * @see #contains(Object) */ public boolean contains(Object objectToCheck, int hash) { checkNotNull(objectToCheck); int index = hash & mask; // using the hashes array for looping and comparison if possible, hence we're cache friendly while (hashes[index] != 0 || table[index] != null) { if (hash == hashes[index] && objectToCheck.equals(table[index])) { return true; } index = ++index & mask; } return false; } @Override public boolean remove(Object objectToRemove) { return remove(objectToRemove, objectToRemove.hashCode()); } /** * Removes the specified element from this set if it is present with * the hash provided in parameter. *

* This variant of {@link #remove(Object)} acts as an optimisation to * enable avoiding {@link #hashCode()} calls if the hash is already * known on the caller side. * * @param objectToRemove object to be removed from this set, if present * @param hash the hash of the element to be removed * @return true if this set contained the specified element * @see #remove(Object) */ public boolean remove(Object objectToRemove, int hash) { checkNotNull(objectToRemove); int index = hash & mask; // using the hashes array for looping and comparison if possible, hence we're cache friendly while (hashes[index] != 0 || table[index] != null) { if (hash == hashes[index] && objectToRemove.equals(table[index])) { removeFromIndex(index); return true; } index = ++index & mask; } return false; } @Override public boolean removeAll(Collection elementsToRemove) { boolean setChanged = false; for (Object objectToRemove : elementsToRemove) { setChanged |= remove(objectToRemove.hashCode()); } return setChanged; } @Override public boolean retainAll(Collection elementsToRetain) { boolean setChanged = false; final int sizeBeforeRemovals = size; int visited = 0; for (int index = 0; index < table.length && visited < sizeBeforeRemovals; index++) { final Object storedElement = table[index]; if (storedElement != null) { visited++; if (!elementsToRetain.contains(storedElement)) { removeFromIndex(index); setChanged = true; } } } return setChanged; } @Override public int size() { return size; } @Override public Iterator iterator() { return new ElementIterator(); } @Override public Object[] toArray() { return toArray(new Object[size]); } @Override @SuppressWarnings("unchecked") public T[] toArray(T[] array) { if (array.length < size) { array = (T[]) new Object[size]; } int arrIdx = 0; for (int i = 0; i < table.length && arrIdx < size; i++) { if (table[i] != null) { array[arrIdx++] = (T) table[i]; } } return array; } @Override public void clear() { size = 0; Arrays.fill(hashes, 0); Arrays.fill(table, null); ++version; } /** * Returns the capacity of the set * * @return the capacity of the set */ public int capacity() { return capacity; } /** * Returns the current memory consumption (in bytes) * * @return the current memory consumption */ @SuppressWarnings("checkstyle:trailingcomment") public long footprint() { return INT_SIZE_IN_BYTES * hashes.length // size of hashes array + REFERENCE_COST_IN_BYTES * table.length // size of table array + REFERENCE_COST_IN_BYTES // reference to hashes array + REFERENCE_COST_IN_BYTES // reference to table array + FLOAT_SIZE_IN_BYTES // loadFactor + INT_SIZE_IN_BYTES // resizeThreshold + INT_SIZE_IN_BYTES // capacity + INT_SIZE_IN_BYTES // mask + INT_SIZE_IN_BYTES // size + INT_SIZE_IN_BYTES; // version } /** * Returns the load factor of the set * * @return the load factor of the set */ public float loadFactor() { return loadFactor; } @Override public int hashCode() { int hashCode = 0; for (int hash : hashes) { hashCode += hash; } return hashCode; } @Override public boolean equals(Object o) { return super.equals(o); } private void increaseCapacity() { final int newCapacity = capacity << 1; if (newCapacity < 0) { throw new IllegalStateException("Max capacity reached at size=" + size); } rehash(newCapacity); } private void rehash(final int newCapacity) { if (1 != Integer.bitCount(newCapacity)) { throw new IllegalStateException("New capacity must be a power of two"); } capacity = newCapacity; mask = newCapacity - 1; resizeThreshold = (int) (newCapacity * loadFactor); final Object[] newTable = new Object[capacity]; final int[] newHashes = new int[capacity]; for (int i = 0; i < table.length; i++) { final Object element = table[i]; if (element != null) { int index = hashes[i] & mask; while (null != newTable[index]) { index = ++index & mask; } newTable[index] = element; newHashes[index] = hashes[i]; } } table = newTable; hashes = newHashes; } private void removeFromIndex(int index) { hashes[index] = 0; table[index] = null; size--; version++; compactChain(index); } private void compactChain(final int indexOfRemoved) { int deleteIndex = indexOfRemoved; int index = deleteIndex; while (true) { index = ++index & mask; if (null == table[index]) { return; } final int hashedIndex = hashes[index] & mask; if ((index < hashedIndex && (hashedIndex <= deleteIndex || deleteIndex <= index)) || (hashedIndex <= deleteIndex && deleteIndex <= index)) { hashes[deleteIndex] = hashes[index]; table[deleteIndex] = table[index]; hashes[index] = 0; table[index] = null; deleteIndex = index; } } } private final class ElementIterator implements Iterator { /** * The version of the set at which the iterator is constructed. * * @see #version */ private final int expectedVersion; private int position; private int index; private ElementIterator() { this.expectedVersion = OAHashSet.this.version; } @Override public boolean hasNext() { return position < size; } @Override @SuppressWarnings("unchecked") public E next() { if (version != expectedVersion) { throw new ConcurrentModificationException(); } for (; index < table.length && position < size; index++) { if (table[index] != null) { position++; // we need to make sure that the index advances, hence index++ return (E) table[index++]; } } throw new NoSuchElementException(); } /** * {@inheritDoc} * *

* * Remove is not permitted in this implementation, since removals * come with compaction, which may cause some elements to be * missed by the iterator. */ @Override public void remove() { throw new UnsupportedOperationException("remove"); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy