HashMap

概述

  HashMap是基于Map接口实现的,允许有null的键/值,非同步,不保证有序(比如插入的顺序),不保证顺序不随时间变化。

两个重要参数

  在HashMap中有两个重要的参数,分别是Capacity(容量)和Load factor(负载因子)。

  Capacity就是bucket的大小,Load factor就是bucket填满程度的最大比例。如果对迭代性能要求很高的话,不要把Capacity设置过大,也不要把Load factor设置过小。当bucket中的entries的数目大于capacity * load factor时,就需要调整bucket的大小为当前的两倍。

put()方法

  put()方法大致的思路如下:
1 . 对key的hashCode()方法做hash,然后再计算index;
2 . 如果没碰撞就直接放到bucket里;
3 . 如果碰撞了,以链表的形式存在bucket后;
4 . 如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链表转换成红黑树;
5 . 如果节点已经存在就替换old value(保证key的唯一性);
6 . 如果bucket满了(超过load factor * current capacity),就要进行resize;

public V put(K key, V value) {
    //对key的hashCode()做hash;
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //tab为空则创建;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //计算index,并对null做处理;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        //如果节点存在;
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //该链为树;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        //该链为链表;
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        //写入;
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    //超过load factor * current capacity,进行resize();
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

get()方法

  get()方法的实现思路,如下:
1 . bucket里的第一个节点,直接命中;
2 . 如果有冲突,则通过key.equals(key)去查找对应的entry。如果是树,则在树中通过key.equals(key)查找,O(logn);如果是链表,则在链表中通过key.equals(key)查找,O(n);

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        //直接命中;
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        //未命中;
        if ((e = first.next) != null) {
            //在树中get;
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            //在链表中get;
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

hash()方法

  在get和put的过程中,计算下标时,先对hashCode进行hash操作,然后再通过hash值进一步地计算下标;

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

  hash()方法的作用是:高16位不变,低16位和高16位做一个异或;

resize()方法

  当使用put()时,如果发现目前的bucket占用程度已经超过了Load Factor所希望的比例,那么就会发生resize。resize的过程,简单地说就是把bucket扩充为2倍,之后重新计算index,把节点再放到新的bucket中。

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
----------------------last line for now----------------------