/* * Copyright (c) 2011-2016 Pivotal Software 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. */ /* * Inspired by fastutils' OpenHashSet implementation at * https://github.com/vigna/fastutil/blob/master/drv/OpenHashSet.drv */ package reactor.util.concurrent; import java.util.Arrays; import java.util.function.Consumer; /** * A simple open hash set with add, remove and clear capabilities only. * <p>Doesn't support nor checks for {@code null}s. * * @param <T> the element type */ public final class OpenHashSet<T> { final float loadFactor; int mask; int size; int maxSize; T[] keys; public OpenHashSet() { this(16, 0.75f); } @SuppressWarnings("unchecked") public OpenHashSet(int capacity, float loadFactor) { this.loadFactor = loadFactor; int c = QueueSupplier.ceilingNextPowerOfTwo(capacity); this.mask = c - 1; this.maxSize = (int)(loadFactor * c); this.keys = (T[])new Object[c]; } public boolean add(T value) { final T[] a = keys; final int m = mask; int pos = mix(value.hashCode()) & m; T curr = a[pos]; if (curr != null) { if (curr.equals(value)) { return false; } for (;;) { pos = (pos + 1) & m; curr = a[pos]; if (curr == null) { break; } if (curr.equals(value)) { return false; } } } a[pos] = value; if (++size >= maxSize) { rehash(); } return true; } public boolean remove(T value) { T[] a = keys; int m = mask; int pos = mix(value.hashCode()) & m; T curr = a[pos]; if (curr == null) { return false; } if (curr.equals(value)) { return removeEntry(pos, a, m); } for (;;) { pos = (pos + 1) & m; curr = a[pos]; if (curr == null) { return false; } if (curr.equals(value)) { return removeEntry(pos, a, m); } } } boolean removeEntry(int pos, T[] a, int m) { size--; int last; int slot; T curr; for (;;) { last = pos; pos = (pos + 1) & m; for (;;) { curr = a[pos]; if (curr == null) { a[last] = null; return true; } slot = mix(curr.hashCode()) & m; if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) { break; } pos = (pos + 1) & m; } a[last] = curr; } } public void clear(Consumer<? super T> clearAction) { if (size == 0) { return; } T[] a = keys; int len = a.length; for (int i = 0; i < len; i++) { T e = a[i]; if (e != null) { clearAction.accept(e); } } Arrays.fill(a, null); size = 0; } @SuppressWarnings("unchecked") void rehash() { T[] a = keys; int i = a.length; int newCap = i << 1; int m = newCap - 1; T[] b = (T[])new Object[newCap]; for (int j = size; j-- != 0; ) { while (a[--i] == null); int pos = mix(a[i].hashCode()) & m; if (b[pos] != null) { for (;;) { pos = (pos + 1) & m; if (b[pos] == null) { break; } } } b[pos] = a[i]; } this.mask = m; this.maxSize = (int)(newCap * loadFactor); this.keys = b; } private static final int INT_PHI = 0x9E3779B9; static int mix(int x) { final int h = x * INT_PHI; return h ^ (h >>> 16); } public boolean isEmpty() { return size == 0; } public int size() { return size; } public Object[] keys() { return keys; } }