HashMap 理解
- Entry Table
/**
<ul><li>The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table;
这里是用来存储真实数据的Entry table static int hash(int h)
/** * Applies a supplemental hash function to a given hashCode, which * defends against poor quality hash functions. This is critical * because HashMap uses power-of-two length hash tables, that * otherwise encounter collisions for hashCodes that do not differ * in lower bits. Note: Null keys always map to hash 0, thus index 0. */ 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); }
这是对于hashCode()补充的一个hash方法,这是为了补救低质量的hash函数。这样做是关键的,因为HashMap可以使用2的幂长度hash tables,可能会遇到hashcode的冲突,低位上区分不出来。Note:null keys总是被hash称0,即index 0.
^ 是异或操作符. >>> 向右移n位,并且以0来补充左边的位数
h >>> 20 向右移动20位
h >>> 12 向右移动12位
h = h ^ h 和异或
h ^ (h >>> ) ^ (h >>> 4) 再两次异或
为什么说HashMap的长度是2的幂,就会导致hashCode低位上的冲突呢?我们可以从注释上知道默认的capacity就是bucket的数量,这需要谈到hashMap的存储结果,如下图所示,我们先把存储的key进行两次hashCode,然后分到桶bucket中,一个bucket可以包含多个entry。具体拿entry来说,就是bucket中存储多个entry的时候,使用entry.next来进行链接到下一个entry上。理解了这个,我们就可以说明为什么hashMap是2的幂的长度就会产生低位上的冲突。假设我们使用默认的16,那么我们只需要hashCode的4位就可以决定存储到那个bucket。一般来说我们的hashCode都是多于bucket的,我们存储到具体哪个bucket使用的方法是module(求余操作符),所以我们可以用后4位的hashCode进行决定存储。然后这里hashMap还假设hashCode是2的幂或者其他次方的幂,则就会造成hashCode后几位很经常的重复,则可能我们的hashCode的就是XXXX0000, XXXX0100, XXXX1000, XXXX1100,实际用到的bucket就是4位。那么就只有两位来决定bucket.所以就会产生低位冲突,从而造成hashMap性能下降。所以这里用hash来预防hashCode也是2的幂次数产生。
static int indexFor(int h, int length)
static int indexFor(int h, int length) { return h & (length-1); }
将hash和index-1进行并.这里来返回Entry[] table的index.这里做的很聪明啊,就是把hash拿出来,然后length-1会把length以后的2进制位都变成1,所以&出来的就是index了
1111110000000000 01111
- public V get(Object key)
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;
}
如果key是null,则默认是map到entry table index0上的, 如果不是null,则进行hash然后按照entry table[index]进行来取,然后按照bucket的entry来匹配如果相等,则返回entry对应的value。否则返回null - public V put(K key, V value)
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
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;
}
先检查key是否为null,如果null,放到table[0]中;否则来取hash,之后来table[index]来找,先判断hash是否等于entry的hash,然后在把key赋值给Object k, 如果k == key或者key.equals(k),那么就新建一个oldValue = 找到的e.value, e.value = 本次的value,然后recordAccess(this),并且return旧值。如果找不到,就对modCount++,就调用addEntry并且返回null. - addEntry
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
这里我们看到了threshold,这个是由capacity * load factor来决定,当大于这个阈值的时候,就的重新rehash.这里我们可以看到addEntry的操作是,新建一个entry,并且把bucketIndex传进来,获取到目前这个index最前面的一个entry,之后新建一个entry,并且next指向最初的entry。所以我们这里看到的操作应该是插入到头部。