Java:HashMap源码解析

HashMap作为我们最常用的数据结构集合之一,有着它特点。我们可以看看其特点。

首先,我们看看HashMap的数据结构模型。HashMap是数组和链表的集合。具有相同hash码为一个集合,以数组的形式存储。而每个hash码相同的数据,以链表的形式存储。
这里写图片描述

在jdk1.8的环境下,我们查看HashMap主要的方法:构造函数、put()和get()
我们先看看构造函数【默认的构造函数】:

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

我们看看loadFactor是容量因子的意思。loadFactor 容量因子为0.75。实际容量因子=实际存储数/容量大小。如果超过0.75,则会扩充容量。

put()方法:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

我们跟进了put()方法,却看到传入了五个参数。第一个是hash(key),然后才是…
我们看看hash()方法的实现:

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

我们看看,hash(key)返回的内容是经过处理的hash值,我们可以通过该运算,减少hash冲突。hash冲突,可以参考:点击参考。经过此运算后,hash冲突的次数就降低了不少。

我们跟进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;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    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;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

putVal()方法有点长。不过我们慢慢分析。
首先,要知道HashMap有个属性是transient Node<K,V>[] table; 该属性就是上述图中的table。
执行tab=table,将table的内容给了tab。然后如果table内容为null,我们就调用resize()相当于重新分配table的大小。
执行p=tab[i = (n-1) & hash],也就是取tab内容中的第(n-1)&hash个。如果当前没null,也就是说第(n-1)&hash个的链表表头为null。我们就为tab[i]新建结点。
如果不为null,也就意味着第(n-1)&hash个是有值的,我们就遍历这个链表。如果找到合适的位置,就将结点加到该链表中。

而get()方法也是一样的。
我们看看get()方法的实现:

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

主要还是getNode()的实现

 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) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

其实主要的思想也是一样的,首先先找到与hash(key)相同的table元素所在的下标,然后我们去遍历该下标。

了解了HashMap的源码结构了,我们可以做出以下总结:
1、HashMap改变了内容后,不能保证顺序是一样的。
2、HashMap中键值对的存储于value无关,只和hash(key)有关。
3、HashMap没有实现线程同步。
4、HashMap允许key和value值为null

猜你喜欢

转载自blog.csdn.net/new_Aiden/article/details/51001587