/*
* 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.util.ArrayDeque;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
import com.github.ggrandes.kvstore.utils.GenericFactory;
import com.github.ggrandes.kvstore.utils.PrimeFinder;
import org.apache.log4j.Logger;
/**
* Native Integer HashMap
* This class is NOT Thread-Safe
*
* @param <V> type of values
*
* @author Guillermo Grandes / guillermo.grandes[at]gmail.com
*/
public class IntHashMap<V> implements Iterable<V> {
private static final Logger log = Logger.getLogger(IntHashMap.class);
private int elementCount; //元素个数
private IntEntry<V>[] elementData; //数组的每个元素是Map中的每个条目
private final float loadFactor; //加载因子
private int threshold; //容量阈值
private int defaultSize = 17; //初始大小
private GenericFactory<V> factory;
/**
* Constructs a new {@code IntHashMap} instance with the specified capacity.
*
* @param capacity the initial capacity of this hash map.
* @param type class for values
*/
public IntHashMap(final int capacity, final Class<V> type) {
factory = new GenericFactory<V>(type);
defaultSize = primeSize(capacity);
if (capacity >= 0) {
elementCount = 0;
elementData = newElementArray(capacity == 0 ? 1 : capacity);
loadFactor = 0.75f; // Default load factor of 0.75
initCache(elementData.length);
computeMaxSize();
} else {
throw new IllegalArgumentException();
}
}
/**
* Constructs a new {@code IntHashMap} instance with default capacity (17).
*
* @param type class for values
*/
public IntHashMap(final Class<V> type) {
this(17, type);
}
@SuppressWarnings("unchecked")
private IntEntry<V>[] newElementArray(int s) {
return new IntEntry[s];
}
/**
* Removes all mappings from this hash map, leaving it empty.
*
* @see #isEmpty
* @see #size
*/
public void clear() {
clear(true);
}
/**
* Clear the map
* @param shrink if true shrink the map to initial size
*/
@SuppressWarnings("unchecked")
public void clear(final boolean shrink) {
clearCache();
if (elementCount > 0) {
elementCount = 0;
}
if (shrink && (elementData.length > 1024) && (elementData.length > defaultSize)) {
elementData = new IntEntry[defaultSize];
}
else {
Arrays.fill(elementData, null);
}
computeMaxSize();
}
private void computeMaxSize() {
threshold = (int) (elementData.length * loadFactor);
}
/**
* Returns the value of specified key.
*
* @param key the key.
* @return the value of the mapping with the specified key, or {@code null}
* if no mapping for the specified key is found.
*/
public V get(final int key) {
//做与操作, 比取模速度更快. 这里与之后,又进行了取模
final int index = (key & 0x7FFFFFFF) % elementData.length;
//根据key获取key所在的条目在在数组中的索引位置, 返回的是key代表的条目.
IntEntry<V> m = elementData[index];
while (m != null) {
//找到key, 返回这个条目对应的值
if (key == m.key) return m.value;
//如果Map上有散列冲突, 则使用next指针寻找同一个key上的下一个条目
//在数组同一个index处的IndexEntry,对于不同的key,可能都在这个index上. 即用链表的形式表示!
m = m.nextInSlot;
}
return null;
}
/**
* Returns whether this map is empty.
*
* @return {@code true} if this map has no elements, {@code false}
* otherwise.
* @see #size()
*/
public boolean isEmpty() {
return (elementCount == 0);
}
/**
* Maps the specified key to the specified value.
*
* @param key the key.
* @param value the value.
* @return the value of any previous mapping with the specified key or
* {@code null} if there was no such mapping.
*/
public V put(final int key, final V value) {
//要放到数组的哪个索引处
int index = (key & 0x7FFFFFFF) % elementData.length;
//获取出这个位置上已经存在的条目
IntEntry<V> entry = elementData[index];
//这个位置上已经有key了, 说明新添加的键值对和原先的key产生了散列冲突!
//那么新添加的KV是添加到链表的表头还是表尾?
while (entry != null && key != entry.key) {
//1)entry是数组中原有的条目, 如果这个条目没有下一个条目, 则entry.nextInSlot=null, 导致entry=null
//2)当然如果数组的index中原先没有条目,entry不会执行while语句, 最后的结果entry=null
//3)如果当前条目entry有下一个条目, 比如k1->k3, entry=k1, k1=k1.next=k3
//则还要进行while循环, k3=k3.next=null,最终也导致entry=null
//4)要添加的元素在数组中已经存在,则entry!=null,最后会进行覆盖!
//所以**put之前实际上是进行了一次链表的扫描!** 以判断链表中是否有要添加的元素.
entry = entry.nextInSlot;
}
if (entry == null) {
//在还没添加元素前,将数量+1, 并用加1后的数量判断是否超过阈值.因为不能在添加之后,才知道达到阈值了!
if (++elementCount > threshold) {
//重新进行hash
rehash();
//计算新的index
index = (key & 0x7FFFFFFF) % elementData.length;
}
//创建一个新的条目
entry = createHashedEntry(key, index);
}
//这个entry现在是我们新添加的元素! 当然如果链表中已经存在该entry,则进行覆盖!
V result = entry.value;
entry.value = value;
return result;
}
//在每次新创建一个条目时, 会加到数组的index位置的链表的表头! 链表表示同一个index的多个有散列冲突的条目!
IntEntry<V> createHashedEntry(final int key, final int index) {
//删除元素时,会留下空闲空间, 才添加元素时, 优先使用空闲空间
IntEntry<V> entry = reuseAfterDelete();
//没有空闲空间, 则只能新创建一条条目了
if (entry == null) {
entry = new IntEntry<V>(key);
} else {
//有空闲空间, 就使用
entry.key = key;
entry.value = null;
}
//entry是最新的条目, 将它的nextInSlot设置为数组中index位置已经存在的条目.
//即新添加的条目在链表的表头!
entry.nextInSlot = elementData[index];
//设置数组index处的链表的表头为当前新创建的条目
elementData[index] = entry;
return entry;
}
//重新hash时, 原先在数组中同一个index的条目,可能会被hash到不同的index中. 因为hash的基础是数组的长度. 数组长度变化,index也会变化.
void rehash(final int capacity) {
//capacity的容量翻倍了
final int length = primeSize(capacity == 0 ? 1 : capacity << 1);
if (log.isDebugEnabled()) log.debug(this.getClass().getName() + "::rehash() old=" + elementData.length + " new=" + length);
//创建一个新的数组
IntEntry<V>[] newData = newElementArray(length);
for (int i = 0; i < elementData.length; i++) {
//原数组中旧的每个条目
IntEntry<V> entry = elementData[i];
while (entry != null) {
//entry表示的是条目的第一个元素, 因为一个条目会有多个键值对
//用新的数组长度计算在新数组中的index,将被用于newData中!
int index = (entry.key & 0x7FFFFFFF) % length;
//找到这个条目的下一个元素
IntEntry<V> next = entry.nextInSlot;
entry.nextInSlot = newData[index];
newData[index] = entry;
//设置下一个要循环的entry为当前entry的next引用
entry = next;
}
}
elementData = newData;
computeMaxSize();
}
void rehash() {
rehash(elementData.length);
}
/**
* Removes the mapping with the specified key from this map.
*
* @param key the key of the mapping to remove.
* @return the value of the removed mapping or {@code null} if no mapping
* for the specified key was found.
*/
public V remove(final int key) {
IntEntry<V> entry = removeEntry(key);
if (entry == null) return null;
V ret = entry.value;
//回收删除的条目
reuseAfterDelete(entry);
return ret;
}
//删除元素要考虑同一个数组index处的元素的指针更新
IntEntry<V> removeEntry(final int key) {
IntEntry<V> last = null;
final int index = (key & 0x7FFFFFFF) % elementData.length;
IntEntry<V> entry = elementData[index];
while (true) {
if (entry == null) return null;
//找到了这个条目
if (key == entry.key) {
//没有执行到if后面的语句, 即第一个条目就是要删除的条目
//entry -> next
//删除链表表头entry后
//数组的index的条目就是entry的下一个条目
if (last == null) {
elementData[index] = entry.nextInSlot;
} else {
//要删除的条目是entry,
//last -> entry -> next
//删除entry后, 要将last的下一个条目指向next
//last --> next
//这里不需要更新elementData[index]因为entry并不是链表的第一个条目. 第一个条目没有变化,就不需要更新!
last.nextInSlot = entry.nextInSlot;
}
elementCount--;
return entry;
}
//循环,在链表中找出要删除的条目
last = entry; //上一个条目
//下一个条目, 使用next指针指向下一个条目.作为下一次循环的条目
entry = entry.nextInSlot;
}
}
/**
* Returns the number of elements in this map.
*
* @return the number of elements in this map.
*/
public int size() {
return elementCount;
}
// ========== Entry Cache
/*
private transient Entry<V> reuseAfterDelete = null;
private void initCache(int size) {}
private void clearCache() {}
private Entry<V> reuseAfterDelete() {
final Entry<V> ret = reuseAfterDelete;
reuseAfterDelete = null;
return ret;
}
private void reuseAfterDelete(final Entry<V> entry) {
entry.clean();
reuseAfterDelete = entry;
}
*/
private ArrayDeque<IntEntry<V>> cache;
private void initCache(final int size) {
cache = new ArrayDeque<IntEntry<V>>(size);
}
public void clearCache() {
cache.clear();
}
private IntEntry<V> reuseAfterDelete() {
return cache.pollLast();
}
private void reuseAfterDelete(final IntEntry<V> entry) {
entry.clean();
//添加到缓存中. 用队列实现,往队列尾部添加元素
cache.offerLast(entry);
}
// ========== Internal Entry 内部条目
static final class IntEntry<V> {
IntEntry<V> nextInSlot; //下一个条目. 因为不同的key可能会散列在数组的同一个索引位置, 所以通过next指针将相同散列的条目链接在一起
int key; //因为是Map,所以Map的每个条目是KeyValue键值对.
V value;
IntEntry(int theKey) {
this.key = theKey;
this.value = null;
}
void clean() {
value = null;
key = Integer.MIN_VALUE;
nextInSlot = null;
}
}
// ========== Prime Finder
private static final int primeSize(final int capacity) {
//return java.math.BigInteger.valueOf((long)capacity).nextProbablePrime().intValue();
return PrimeFinder.nextPrime(capacity);
}
// ========== Iterator
/**
* @returns iterator over values in map
*/
public Iterator<V> iterator() {
return new IntHashMapIterator<V>(this);
}
/**
* 内部类, 迭代器, 迭代获取Map中的条目. 因为Map的条目使用数组存储.
* 数组的每个元素会存在散列冲突,冲突的条目会以链表的形式保存. 在访问数组的元素时,
* 会首先访问数组索引的第一个条目,然后使用next指针,依次访问链表的每个条目元素. 当链表结束后,访问数组的下一个索引.
*
* associateMap.elementData
* index IntEntry IntEntry
* 0 --> [K1,V1]
* 1 -->
* 2 --> [K2,V2] --> [K4,V4]
* 3 --> [K6,V6] --> [K3,V3] --> K[8,V8]
* 4 --> [K7,V7]
*
* 第一次调用hasNext, entry=null, newPos=pos=0, ele[newPos]!=null, result=true, pos=0 *指向第一个数组索引
* hasNext=true --> next: _entry=entry=null, result=lastEntry=ele[pos=0]=ele[0],
* pos+1=1 *指向下一个数组索引
* entry=lastEntry.next=ele[0].next=null 数组第一个索引只有一个条目,当前这个索引元素没有下一个条目了
*
* 调用hasNext, entry=null, newPos=pos=1, ele[1]=null, newPos+1=2, result=false, pos=newPos=2 指向下一个数组索引 *
* 调用hasNext, entry=null, newPos=pos=2, ele[2]!=null, result=true, pos=newPos=2
* haxNext=true --> next: _entry=entry=null,
* result=lastEntry=ele[pos=2]=ele[2]=K2
* pos+1=3 *指向下一个数组索引
* entry=lastEntry.next=ele[2].next=K2.next=K4
* 调用hasNext, entry!=null, return true
* haxNext=true --> next: _entry=entry=K4!=null,
* lastEntry.next=K2.next=K4 == _entry
* result=_entry=K4
* entry=_entry.next=K4.next=null
*
* 当调用next时, 如果是第一次访问数组的索引(总是先访问数组索引的第一个条目,然后接着链表后的其他条目).
* 设置result=lastEntry=数组索引的第一个条目, 并设置entry=lastEntry.next. 最后返回result, 即当前元素.
*
* ********************************************************************************************
*
* 第一次调用数组的索引时, 指向的是这个数组索引元素的第一个条目.在返回第一个条目前,设置entry=lastEntry.next
* 当然如果数组索引只有一个条目时, 因为没有下一个条目了,所以entry=null.
* 第二种情况就是数组索引不止一个条目, 则设置entry为下一个条目!
* ____
* |||||| EOL(EndOfLinkedList)
* lastEntry entry=lastEntry.next=null
* result!
* ____ ____
* |||||| --> | |
* lastEntry entry
* result!
*
* 当访问链表的下一个元素(第二个)时, lastEntry指向的是上一次访问的条目, 而entry的值在上一步已经给出了, 所以result=entry
* 并且为了统一entry的概念是:指向下一个条目, 要设置entry=entry.next. 这里同样面临者是否有下一个条目的问题.
*
* 下图中的两种情况对应了是否有下一个条目. *==>是调用next前后发生的变化.
* ____ ____ ____ ____
* | | --> |||||| (EOL) *==> | | --> |||||| (EOL)
* lastEntry entry lastEntry | entry=entry.next=null!
* result! result!
* ____ ____ ____ ____ ____ ____
* | | --> |||||| --> | | *==> | | --> |||||| --> | |
* lastEntry entry lastEntry | entry
* result! result!
*
* 注意作为数组索引的第二个条目,并没有更新lastEntry. 因为在访问第一个条目时,lastEntry虽然指向第一个条目,但是
* 作为lastEntry的含义上一个条目来说,数组索引的第一个条目并没有上一条条目! 所以当访问第二个条目时,lastEntry
* 仍然指向第一个条目,它的字面含义上一个条目就说的通了,因为第二个条目的上一个条目是第一个条目.
*
* 来看下访问第三个条目发生了什么变化:
* ____ ____ ____ ____ ____ ____ __ __ ____
* | | --> | | --> |||||| *==> | | --> | | --> |||||| *==> | | -> | | -> |||||| (EOL)
* lastEntry entry lastEntry entry last result! entry=null!
* (A) (B) (C)
* 现在我们访问到了第三个条目(A),第三个条目的上一个条目应该是第二个条目:
* 判断条件: lastEntry.next != entry, 设置lastEntry=lastEntry.next. 上图的中图(B)描述了这个变化.
* 上图的右图(C)表示了entry就是我们要返回的result. 并且由于当前节点是链表的最后一个节点,所以下一个节点entry=null!
* 当然如果当前访问的节点不是链表的最后一个节点, 则entry指向的是下一个节点. 以此类推...
*
*/
static class IntHashMapIterator<V> implements Iterator<V> {
private int position = 0; //数组的索引. 当访问到数组的一个新索引时,position+1
boolean canRemove = false;
IntEntry<V> entry; //下一个条目, 当访问到数组的一个索引的链表的最后一个条目时,entry=null
IntEntry<V> lastEntry; //上一个条目
final IntHashMap<V> associatedMap;
IntHashMapIterator(IntHashMap<V> hm) {
associatedMap = hm;
}
@Override
public boolean hasNext() {
//entry指向的就是下一个条目. 所以判断是否有下一个条目时,如果entry不为空,就说明有下一个元素
//所以在调用next的时候,就应该在获取到元素之后, 把entry指向下一个元素(链表的下一个元素)
//当到达一个数组索引元素的链表的尾部时,会使得entry=null,这时候要重新从下一个数组索引开始
if (entry != null) {
return true;
}
//数组
IntEntry<V>[] elementData = associatedMap.elementData;
int length = elementData.length;
//如果在链表里,则不会执行这里. 如果第一次访问数组的某个索引,已经把position+1了.
//这样第一次访问数组的下一个索引时,position的值就是上一个数组索引+1的值!
int newPosition = position;
boolean result = false;
//不能超过数组的长度
while (newPosition < length) {
//数组的某个索引位置没有条目,则找下一个索引. 因为key散列到的数组位置可能有些index没有分布到条目
if (elementData[newPosition] == null) {
//如果访问的这个数组索引为空,则跳过这个数组索引,访问下一个索引.
//如果没有这段话,在while前newPosition=position, 在while后position=newPosition其实没任何变化
//但正是因为这种第一次访问数组的索引时,可能这个位置并没有条目,所以position应该指向下一个数组的索引.
//所以position的自增操作发生在两个地方: 第一次访问数组的某个索引. 只要第一次访问,不管这个数组元素有没有条目,都+1!
//不同的是如果访问的数组索引位置为空,在hasNext时就自增, 而访问的数组索引位置有条目,在调用next时才自增!
//总的来说: position指向的是下一个数组索引.
newPosition++;
}
else {
//只有在entry==null时:第一次访问到数组的某个索引,如果这个数组有条目,则表示有下一个条目
//如果不是第一次访问数组的这个索引,比如在索引的链表里, 因为entry!=null,则不会执行到这里
//第一次访问数组的这个索引: 指的不仅仅是第一次调用hasNext,
//而是一个数组的某个索引访问完毕,第一次访问下一个数组的索引.
//我们把##非第一次##访问数组的某个索引,表示为之前访问过数组的这个索引,
//下一次的调用还是在这个数组所在的索引里:即访问的是链表.
result = true;
break;
}
}
//访问到了哪个数组的索引. 在同一个数组索引的链表内,entry!=null,不会执行这里.因为position没有变化!
position = newPosition;
return result;
}
@Override
public V next() {
//在调用next获取元素前,先判断是否有下一个元素
if (!hasNext()) throw new NoSuchElementException();
IntEntry<V> result;
IntEntry<V> _entry = entry;
//什么时候entry=null, 第一次访问时, 或者数组的一个index访问完毕,整个index的链表位置访问完毕了.则要访问数组的下一个index!
if (_entry == null) {
//result = lastEntry = associatedMap.elementData[position] ; position++
//result指向的是数组的index位置=position的链表的第一个条目. 在定位到数组新的index时,要将position索引加1指向数组的下一个index!
result = lastEntry = associatedMap.elementData[position++];
//将entry指向当前访问的元素的下一个元素. 如果lastEntry是数组的index的链表的最后一个条目,则会导致entry=lastEntry.next=null
entry = lastEntry.nextInSlot;
} else {
//在数组的index的链表的第二个元素开始....调用next时,都在整个链表内操作. 即数组的同一个index的链表内!
if (lastEntry.nextInSlot != _entry) {
lastEntry = lastEntry.nextInSlot;
}
result = _entry;
entry = _entry.nextInSlot;
}
canRemove = true;
return result.value;
}
@Override
//添加节点添加到链表表头, 删除节点时, 也应该首先删除链表表头节点
//TODO remove什么时候被调用?
public void remove() {
if (!canRemove) throw new IllegalStateException();
canRemove = false;
if (lastEntry.nextInSlot == entry) {
while (associatedMap.elementData[--position] == null) {
// Skip
}
//ele[position]是数组索引的第一个条目/节点. 删除第一个节点后, 原先第二个节点会作为第一个节点
//first = first.next. 这里的first即associatedMap.elementData[position]
associatedMap.elementData[position] = associatedMap.elementData[position].nextInSlot;
entry = null;
}
else {
lastEntry.nextInSlot = entry;
}
if (lastEntry != null) {
IntEntry<V> reuse = lastEntry;
lastEntry = null;
associatedMap.reuseAfterDelete(reuse);
}
associatedMap.elementCount--;
}
}
// =========================================
/**
* Return an array with values in this map
* @return array with values
*/
public V[] getValues() {
final V[] array = factory.newArray(elementCount);
int i = 0;
//this使用for循环,所以是个Iterator. V是调用Iterator.next获取的value
for (final V v : this) {
array[i++] = v;
}
return array;
}
// =========================================
@SuppressWarnings("unused")
public static void main(String[] args) {
if (false) {
long capacity = 1;
int count = 1;
while (capacity < Integer.MAX_VALUE) {
capacity = java.math.BigInteger.valueOf(capacity).nextProbablePrime().longValue();
System.out.print(capacity + ", ");
final double inc = Math.log(2)/Math.log(capacity<<5) * 10 + 1;
//System.out.println(inc);
capacity *= inc;
if (count % 5 == 0) System.out.println();
count++;
}
System.out.println(Integer.MAX_VALUE);
System.out.println("------");
System.out.println(count);
System.out.println(PrimeFinder.nextPrime((int)1e6));
}
if (true) {
IntHashMap<Integer> hash = new IntHashMap<Integer>(16, Integer.class);
hash.put(1, 2);
hash.put(2, 4);
System.out.println("使用for模式:");
for (Integer i : hash.getValues()) {
System.out.println(i);
}
System.out.println("hash大小:"+hash.size());
System.out.println("使用迭代模式:");
for (IntHashMapIterator iterator = (IntHashMapIterator) hash.iterator(); iterator.hasNext();) {
Integer intValue = (Integer)iterator.next();
System.out.println(intValue);
//iterator.remove();
}
System.out.println("hash大小:"+hash.size());
}
}
}