/*
* 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.github.ggrandes.kvstore.structures.hash;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import com.github.ggrandes.kvstore.utils.PrimeFinder;
/**
* A hashtable-based Set implementation with weak values. An entry in a
* 基于hashTable实现的集合, 使用weak值. WeakSet中的一个条目会被自动移除: 它的值不再被经常使用时.
* WeakSet will automatically be removed when its value is no longer in
* ordinary use. More precisely, the presence of a given value will
* 更精确地说:给定一个值,如果它存了, 也不会阻止垃圾回收其将它回收.
* not prevent the value from being discarded by the garbage collector, that is,
* made finalizable, finalized, and then reclaimed. When a value has been
* discarded its entry is effectively removed from the set, so this class
* 当一个值被丢弃后,它的条目也会从集合中被删除掉.
* behaves somewhat differently from other Set implementations.
* <p>
* This class is NOT Thread-Safe
*
* @see java.util.WeakHashMap
*
* @author Guillermo Grandes / guillermo.grandes[at]gmail.com
*/
public class WeakSet<T> {
private int elementCount;
private Entry<T>[] elementData;
private final float loadFactor;
private int threshold;
private int defaultSize = 17;
/**
* Reference queue for cleared WeakEntry 引用队列,用来清除条目
*/
private final ReferenceQueue<T> queue = new ReferenceQueue<T>();
/**
* Constructs a new {@code WeakSet} instance with the specified capacity.
*
* @param capacity the initial capacity of this set.
* @param type class for values
*/
public WeakSet(final int capacity) {
defaultSize = primeSize(capacity);
if (capacity >= 0) {
elementCount = 0;
elementData = newElementArray(capacity < 0 ? 1 : capacity);
loadFactor = 0.75f; // Default load factor of 0.75
computeMaxSize();
} else {
throw new IllegalArgumentException();
}
}
/**
* Constructs a new {@code WeakSet} instance with default capacity (17).
*/
public WeakSet() {
this(17);
}
/**
* Check for equal objects
*
* @param o1
* @param o2
* @return true if equals
*/
private final static boolean eq(Object o1, Object o2) {
return ((o1 == o2) || o1.equals(o2));
}
/**
* Removes all values from this WeakSet, leaving it empty.
*
* @see #isEmpty
* @see #size
*/
public final void clear() {
clear(true);
}
/**
* Clear the set
*
* @param shrink if true, shrink the set to initial size
*/
public void clear(final boolean shrink) {
while (queue.poll() != null);
if (elementCount > 0) {
elementCount = 0;
}
if (shrink && (elementData.length > 1024) && (elementData.length > defaultSize)) {
elementData = newElementArray(defaultSize);
} else {
Arrays.fill(elementData, null);
}
computeMaxSize();
while (queue.poll() != null);
}
/**
* Returns the specified value.
*
* @param value the value.
* @return the value, or {@code null} if not found the specified value
*/
public T get(final T value) {
expungeStaleEntries();
//
final int index = (value.hashCode() & 0x7FFFFFFF) % elementData.length;
Entry<T> m = elementData[index];
while (m != null) {
if (eq(value, m.get()))
return m.get();
//由于不同节点可能散列到数组的同一个索引上. 这些不同的节点会以散列表的形式保存
//所以在获取value对应的节点时,当定位到数组的索引index后. 还不能立即确定第一个节点就是要查的.
//因为这个数组索引上是一个链表节点. 所以如果没有找到,就遍历链表. 通过条目的next引用,不断往右移动指针来控制循环.
m = m.nextInSlot;
}
return null;
}
/**
* Returns whether this set is empty.
*
* @return {@code true} if this set has no elements, {@code false} otherwise.
* @see #size()
*/
public final boolean isEmpty() {
return (size() == 0);
}
/**
* Puts the specified value in the set.
*
* @param value the value.
* @return the value of any previous put or {@code null} if there was no such value.
*/
public T put(final T value) {
expungeStaleEntries();
//
final int hash = value.hashCode();
int index = (hash & 0x7FFFFFFF) % elementData.length;
Entry<T> entry = elementData[index];
while (entry != null && !eq(value, entry.get())) {
//和IntHashMap的逻辑类似,会遍历数组索引上的链表节点, 因为链表中可能已经有这个节点了,则进行更新!
entry = entry.nextInSlot;
}
if (entry == null) {
if (++elementCount > threshold) {
expandElementArray(elementData.length);
index = (hash & 0x7FFFFFFF) % elementData.length;
}
entry = createHashedEntry(value, index);
return null;
}
final T result = entry.get();
return result;
}
private final Entry<T> createHashedEntry(final T valye, final int index) {
//纵观本类,并没有找到queue.offer.而对于队列而言,加入队列,才使得队列中有元素.
//这里在创建一个条目的时候,传递了queue对象,实际上就是把当前Entry条目加入到了queue中了!
Entry<T> entry = new Entry<T>(valye, queue);
//类似于HashMap底层使用数组来存储时,同一个数组索引位置会有多个条目.
//所以设置新创建的条目是数组索引的第一个条目②.它的下一个条目指向原先的第一个条目①
entry.nextInSlot = elementData[index]; //①
elementData[index] = entry; //②
return entry;
}
private final void computeMaxSize() {
threshold = (int) (elementData.length * loadFactor);
}
@SuppressWarnings("unchecked")
private final Entry<T>[] newElementArray(int s) {
return new Entry[s];
}
private final void expandElementArray(final int capacity) {
final int length = primeSize(capacity < 0 ? 1 : capacity << 1);
final Entry<T>[] newData = newElementArray(length);
//循环将旧数组中的条目复制到新扩容的数组中
for (int i = 0; i < elementData.length; i++) {
Entry<T> entry = elementData[i];
//回收旧数组的空间.
elementData[i] = null;
while (entry != null) {
//数组索引存储者链表, 取到一个条目后, 要找到下一个条目, 下一个条目也要重新放到扩容数组中.
final Entry<T> next = entry.nextInSlot;
final T value = entry.get();
if (value == null) {
//在扩容时,如果这个条目的值为空, 说明已经被垃圾回收了,则不放入新数组中
entry.nextInSlot = null;
elementCount--;
} else {
//计算旧数组中的条目在新数组中的索引位置
final int index = (entry.hash & 0x7FFFFFFF) % length;
//最近取到的条目胡会放在链表的表头.类似于添加条目时,加在链表表头.
//比如数组索引index中已经有一个条目了,一个新的条目如果其index相同,则新条目会加到旧条目的前面
entry.nextInSlot = newData[index]; //加入一个新条目时,要做2件事情, 一是更新next引用=旧的数组索引的第一个条目
newData[index] = entry; //二是将自己设为数组索引的第一个条目.
}
//下次循环的节点是当前节点的下一个节点.以此类推
entry = next;
}
}
elementData = newData;
computeMaxSize();
}
//删除过期的条目
@SuppressWarnings("unchecked")
private final void expungeStaleEntries() {
Entry<T> entry;
//从queue中弹出一个条目. 因为在创建条目的时候传入了queue实际上就是将条目加入到queue中
//一旦从queue中要弹出一个条目,这个条目就是要被删除的了!
while ((entry = (Entry<T>) queue.poll()) != null) {
final int i = (entry.hash & 0x7FFFFFFF) % elementData.length;
//在数组中的位置. 注意prev不一定就是我们要删除的节点entry!
Entry<T> prev = elementData[i];
Entry<T> p = prev;
while (p != null) {
//下一个条目
Entry<T> next = p.nextInSlot;
//找到要删除的节点. 即当前循环访问到的节点p == 要删除的节点entry
if (p == entry) {
if (prev == entry) {
//如果是数组索引的第一个节点. 则删除第一个条目后,下一个条目next变成第一个条目了elementData[i].
elementData[i] = next;
} else {
//不是第一个节点,因为我们知道当前要删除的节点的上一个节点是prev, 将prev的next设置成next. 这样entry就会从中间被删除了.
prev.nextInSlot = next;
}
//回收entry
entry.nextInSlot = null;
elementCount--;
break;
}
//如果循环的节点p不是我们要删除的节点
prev = p; //记录本次处理的节点,这样下一次循环时,知道prev上次处理了哪个节点.
//记录prev的目的是找到要删除的节点后,这个prev的下一个节点引用会被更新为要删除的节点的下一个节点.
p = next; //将p指向下一个节点,这样下次循环时, 针对的就是下一个节点了. 以此类推,在没找到时,一直下一个
}
}
}
/**
* Removes the specified value from this set.
*
* @param value the value to remove.
* @return the value removed or {@code null} if not found
*/
public T remove(final T value) {
expungeStaleEntries();
//
final Entry<T> entry = removeEntry(value);
if (entry == null)
return null;
final T ret = entry.get();
return ret;
}
private final Entry<T> removeEntry(final T value) {
Entry<T> last = null;
final int index = (value.hashCode() & 0x7FFFFFFF) % elementData.length;
Entry<T> entry = elementData[index];
while (true) {
if (entry == null)
return null;
if (eq(value, entry.get())) {
if (last == null) {
elementData[index] = entry.nextInSlot;
} else {
last.nextInSlot = entry.nextInSlot;
}
elementCount--;
return entry;
}
last = entry;
entry = entry.nextInSlot;
}
}
/**
* Returns the number of elements in this set.
*
* @return the number of elements in this set.
*/
public int size() {
if (elementCount == 0)
return 0;
expungeStaleEntries();
return elementCount;
}
// ========== Internal Entry
//WeakSet中的条目继承了软引用. 软引用对象在不使用时会被GC回收
private static final class Entry<T> extends WeakReference<T> {
private final int hash;
private Entry<T> nextInSlot;
private Entry(final T value, ReferenceQueue<T> queue) {
super(value, queue);
hash = (value.hashCode() & 0x7FFFFFFF);
}
}
// ========== Prime Finder
private static final int primeSize(final int capacity) {
return PrimeFinder.nextPrime(capacity);
}
}