转载请注明出处chendong_
http://blog.csdn.net/chendong_/article/details/49834325
private transient Set<Entry<K, V>> entrySet;
transient HashMapEntry<K, V>[] table;
static class HashMapEntry<K, V> implements Entry<K, V> {
final K key;
V value;
final int hash;
HashMapEntry<K, V> next;
HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) {
this.key = key;
this.value = value;
this.hash = hash;
this.next = next;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
@Override public final boolean equals(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry<?, ?> e = (Entry<?, ?>) o;
return Objects.equal(e.getKey(), key)
&& Objects.equal(e.getValue(), value);
}
@Override public final int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}
@Override public final String toString() {
return key + "=" + value;
}
}
private static int secondaryHash(int h) {
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
//计算hash值,使用indexFor()这个函数获得该哈希值对应数组下标,由此可以得到对应的那个链表。
int i = indexFor(hash, table.length );
for (Entry<K,V> e = table [i]; e != null; e = e.next) {
Object k;
//关注这部分代码是替换掉已经存在的key对应的value
if (e.hash == hash && ((k = e.key ) == key || key.equals(k))) {
V oldValue = e. value;
e. value = value;
e.recordAccess( this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null ;
}
//解释一下这部分代码,做的操作就是将新添加的Entry放在链表的头部,而原来的链表会接在这个新的Entry后面。
void addEntry(int hash, K key, V value, int bucketIndex) {
//注意这里的bucketIndex是上一个函数得到的数组下标。取到该数组项链接的Entry给了临时变量e
Entry<K,V> e = table [bucketIndex];
//新建了一个Entry将其链接到了数组项上,然后把e给了新的Entry的Next,也就是后面的链表重新链接到了新的Entry后面,而新的Entry作为了该链表的第一项。
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
//添加之后重新计算容量。
if (size ++ >= threshold)
resize(2 * table.length );
}
@Override
public V put(K key, V value) {
if (key == null) {
return putValueForNullKey(value);
}
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
if (e.hash == hash && key.equals(e.key)) {
preModify(e);
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
// No entry for (non-null) key is present; create one
modCount++;
//注意这里是先进行扩容后进行添加的
if (size++ > threshold) {
tab = doubleCapacity();
index = hash & (tab.length - 1);
}
addNewEntry(key, value, hash, index);
return null;
}
//Android添加代码就更简单了,不过也很好理解,操作与java中是一样的。不过方法很巧//妙,简化了代码,让我想起了InputStream is = new FileInputStream("path")is = new BufferedInputStream(is)`
void addNewEntry(K key, V value, int hash, int index) {
table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);
}
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table [indexFor(hash, table.length )];
e != null;
e = e. next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value ;
}
return null;
}
public V get(Object key) {
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
return e == null ? null : e.value;
}
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
return e.value;
}
}
return null;
}
static final int DEFAULT_INITIAL_CAPACITY = 16;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
//默认的构造方法会初始化一个容量是16,负载因子0.75的HashMap,threshold有阈值的意思,就是当前容量可以承受的负载,当超过这个负载时就会进行扩容。
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int )(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR );
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
//指定容量和负载因子时会检查容量和负载因子的值是不是有问题,然后会进行一个位运算算法获得-》大于当前要求的容量的-》最小的2的幂(也就是如果是7--》8如果是9--》16)
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +loadFactor);
// Find a power of 2 >= initialCapacity
//算法原理:capacity初始为1,当capacity小于initialCapacity时左移一位,就是*2,直到找到大于当前要求的容量最小的2的幂
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int )(capacity * loadFactor);
table = new Entry[capacity];
init();
}
//容量增加,transfer(newTable);会将原来的数据再哈希重新放入数据
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length ;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
//默认的初始最小容量是4,一个静态的初始数组的容量是最小容量无符号右移1位,也就是2,默认构造时,会将该静态数组交给table,这里有一行源码的注释( Forces first put invocation to replace EMPTY_TABLE),意思是根据第一次put方法加入的数量来扩充数组,此时负载容量是-1,添加时会立刻扩充容量,这可能也是为什么Android中添加元素是先检查扩充容量再添加的原因。。在put时第一次百分百检测到容量不够,此时进行一次doubleCapacity()将容量加倍,在这个方法中会调用
private static final int MINIMUM_CAPACITY = 4;
private static final Entry[] EMPTY_TABLE
= new HashMapEntry[MINIMUM_CAPACITY >>> 1];
public HashMap() {
table = (HashMapEntry<K, V>[]) EMPTY_TABLE;
threshold = -1; // Forces first put invocation to replace EMPTY_TABLE
}
//容量增长或初始化时会调用该方法,重置阈值,与Java稍有不同,使用固定的容量的3/4来作为阈值,Java中还要比较容量的大小,取Math.min(容量,)
private HashMapEntry<K, V>[] doubleCapacity() {
HashMapEntry<K, V>[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
return oldTable;
}
int newCapacity = oldCapacity * 2;
HashMapEntry<K, V>[] newTable = makeTable(newCapacity);
if (size == 0) {
return newTable;
}
for (int j = 0; j < oldCapacity; j++) {
/* * Rehash the bucket using the minimum number of field writes. * This is the most subtle and delicate code in the class. */
HashMapEntry<K, V> e = oldTable[j];
if (e == null) {
continue;
}
int highBit = e.hash & oldCapacity;
HashMapEntry<K, V> broken = null;
newTable[j | highBit] = e;
for (HashMapEntry<K, V> n = e.next; n != null; e = n, n = n.next) {
int nextHighBit = n.hash & oldCapacity;
if (nextHighBit != highBit) {
if (broken == null)
newTable[j | nextHighBit] = n;
else
broken.next = n;
broken = e;
highBit = nextHighBit;
}
}
if (broken != null)
broken.next = null;
}
return newTable;
}
private HashMapEntry<K, V>[] makeTable(int newCapacity) {
@SuppressWarnings("unchecked") HashMapEntry<K, V>[] newTable
= (HashMapEntry<K, V>[]) new HashMapEntry[newCapacity];
table = newTable;
//阈值时是容量的3/4,使用了位运算
threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity
return newTable;
}
//同样的检查机制,然后使用了一个方法,又是Collections类中的方法Collections.roundUpToPowerOfTwo(capacity);,作用是一样的,将容量修改为大于输入容量的最小的2的幂。但是使用二进制实现,会不会更牛逼一点。
public HashMap(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("Capacity: " + capacity);
}
if (capacity == 0) {
@SuppressWarnings("unchecked")
HashMapEntry<K, V>[] tab = (HashMapEntry<K, V>[]) EMPTY_TABLE;
table = tab;
threshold = -1; // Forces first put() to replace EMPTY_TABLE
return;
}
if (capacity < MINIMUM_CAPACITY) {
capacity = MINIMUM_CAPACITY;
} else if (capacity > MAXIMUM_CAPACITY) {
capacity = MAXIMUM_CAPACITY;
} else {
capacity = Collections.roundUpToPowerOfTwo(capacity);
}
makeTable(capacity);
}
//看不懂啊,应该收藏一下关于二进制的算法,高大上的别人都看不懂
public static int roundUpToPowerOfTwo(int i) {
i--; // If input is a power of two, shift its high-order bit right.
// "Smear" the high-order bit all the way to the right.
i |= i >>> 1;
i |= i >>> 2;
i |= i >>> 4;
i |= i >>> 8;
i |= i >>> 16;
return i + 1;
}
int index = hash & (tab.length - 1);
在java中有相同的设计是一个函数indexfor(int hash)
在计算hashcode对应的数组下标时使用hash&len-1代替了hash%len,只有在len是2的幂时,这两个方法才是等价不等效的。可能不太好理解,举个例子:len是16时,二进制10000,len-1是01111,如果得到的hash小于16时,比如是3吧,进行&运算01111&00011得到的结果是3,也就是本身。确实等价于3%16,再比如大于16,是19吧,再大也是一个意思,也就是01111&10011得到的结果是3,等价于19%16,右数第5位以上的1都被过滤掉了,说这么多只是解释一下确实hash&len-1等价于hash%len,但是使用位运算效率会大大提高,在源码中位运算随处可见。ps:我总觉的还有别的作用,但是没有了解到。