HashMap 理解

来源:互联网 发布:ajax解析嵌套json数据 编辑:程序博客网 时间:2024/06/05 07:06

HashMap 理解

  1. Entry Table

    /**
    <ul><li>The table, resized as necessary. Length MUST Always be a power of two.
    */
    transient Entry[] table;

    这里是用来存储真实数据的Entry table
  2. 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的幂次数产生。
    摘自SO的图

  3. 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了

    111111000000000001111
  4. 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
  5. 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.
  6. 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。所以我们这里看到的操作应该是插入到头部。
0 0
原创粉丝点击