Init.Sun Chengdu.Sichuan

hashMap源码分析笔记

2018-10-29
Init

hashMap源码分析笔记:JDK7:数组+单链表;JDK8:数组+链表+红黑树(高性能的平衡树)

数据结构

数据结构 存储方案 实现
数组 一片物理上连续的大小确定的存储空间 int[]
顺序表(对数组的封装) 物理上连续;逻辑上连续;大小可以动态增加 ArrayList
链表 物理上不连续;逻辑上连续;可以动态增删节点 LinkedList

ArrayList在插入/删除的时候,首先将元素删除,然后再将其他所以的元素重新赋值(前移一位),不适合频繁删除/添加,效率低

LinkedList : pre<-data->next Node链表形式,增删时只需要修改指向,适用于频繁的增加删除

顺序表 链表  
优点 物理上连续,所以寻找快捷。查找快 空间不连续,逻辑上连续。增删快
缺点 增删元素要大量移动数据。增删慢 物理上不连续,查找要轮询。查找慢

Hash表

数组 + 链表


// HashMap部分源码

transient Node<K,V>[] table;  // 数组 + 链表

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
    ...
}

数组和链表如何组织工作

数组存储hash%10的下标值,链表存储实际value,当hash碰撞时,value以链表的形式存在

Hash的原理是什么

hash()位运算得出hash值

hashCode及HashMap中的hash()函数

Hash的put方法原理


// JDK1.7
/**
    * Associates the specified value with the specified key in this map.
    * If the map previously contained a mapping for the key, the old
    * value is replaced.
    *
    * @param key key with which the specified value is to be associated
    * @param value value to be associated with the specified key
    * @return the previous value associated with <tt>key</tt>, or
    *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
    *         (A <tt>null</tt> return can also indicate that the map
    *         previously associated <tt>null</tt> with <tt>key</tt>.)
    */
public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);

    // 根据key计算其对应的hash值,然后再计算对应的数组下标
    int hash = hash(key);
    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;
}


// 防止低质量的哈希函数
 /**
    * Retrieve object hash code and applies a supplemental hash function to the
    * result hash, 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.
    */
final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();

    // 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);
}
// 得到数组下标
/**
    * Returns index for hash code h.
    */
static int indexFor(int h, int length) {
    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    return h & (length-1);
}

hashCode及HashMap中的hash()函数

如果key是自己实现的类,一定要去实现hashCode和equals方法

参考:HashMap中如果key是自定义的类,为什么重写hashcode()和equals()

addEntry()


void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    createEntry(hash, key, value, bucketIndex);
}

// new Entry<>(hash, key, value, e);
// 以链表的形式向前添加,注意这个e,就是Entry有参构造中的n
void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

// n --> next
Entry(int h, K k, V v, Entry<K,V> n) {
    value = v;
    next = n;
    key = k;
    hash = h;
}

Hash的get方法原理


// JDK1.7

public V get(Object key) {
    if (key == null)
        return getForNullKey();
    Entry<K,V> entry = getEntry(key);

    return null == entry ? null : entry.getValue();
}

final Entry<K,V> getEntry(Object key) {
    if (size == 0) {
        return null;
    }

    int hash = (key == null) ? 0 : hash(key);
    // 轮询该数组下标中的链表,链表元素hash和key一致则返回
    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 != null && key.equals(k))))
            return e;
    }
    return null;
}

hash碰撞

计算出来相同的hash值

通常有两类方法处理碰撞:开放寻址(Open Addressing)法和链接(Chaining)法。

HashMap和Hashtable

Hashtable只在hashMap的基础上添加了一个关键字:synchronized,即线程安全

其他

参考:

手写简单HashMap

揭秘 HashMap 实现原理(Java 8)


Similar Posts

Comments