Java容器之HashMap

   Java中容器主要分为两大类:Collection和Map,让我们来看一下Map下主要的继承类和实现类。本篇主要学习HashMap源码。
image

   与新的集合实现不同,HashTable是同步的。如果不需要线程安全的实现,使用HashMap代替HashTable。如果需要线程安全且高度并发地实现,则使用ConcurrentHashMap替代HashTable。如若非要用HashMap,则可以用 Collections.synchronizedMap(Map map) 。

   那么HashMap底层是如何存储数据的呢?

   简单地来说,在jdk1.7之前采用的是链地址法进行存储数据,即链表和数组结合的方法。jdk1.7源码如下:

//HashMap数据存储数据
transient Entry[] table;
//HashMap的数据都是存储在Entry数组中的。
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    final int hash;

    /**
     * Creates new entry.
     *  输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)"
     */
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

    public final K getKey() {
        return key;
    }

    public final V getValue() {
        return value;
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry e = (Map.Entry)o;
        Object k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            Object v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }

    public final int hashCode() {
        return (key==null   ? 0 : key.hashCode()) ^
               (value==null ? 0 : value.hashCode());
    }

    public final String toString() {
        return getKey() + "=" + getValue();
    }

    /**
     * This method is invoked whenever the value in an entry is
     * overwritten by an invocation of put(k,v) for a key k that's already
     * in the HashMap.
     */
    void recordAccess(HashMap<K,V> m) {
    }

    /**
     * This method is invoked whenever the entry is
     * removed from the table.
     */
    void recordRemoval(HashMap<K,V> m) {
    }
}

   HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,当插入的Entry越来越多的时候,就会发生冲突,HashMap数组的每一个元素不只是一个Entry对象,也是一个链表的头结点,每一个Entry对象通过Next指针指向下一个节点,当发生冲突时,只需要插入到对应得链表位置即可。

   HashMap初始值默认是16,原因是为了服务于从key到index映射的Hash算法。为了实现一个尽量均匀分布的Hash算法,传统的取模运算固然简单,但是效率低下,为了提高效率,采用位运算。计算公式是:index = HashCode(Key) & (Length - 1),其中,length是HashMap的长度。

   如果 hash 值冲突较多的情况下,链表的长度就会越来越长,此时通过单链表来寻找对应 Key 对应的 Value 的时候就会使得时间复杂度达到 O(n),因此在 JDK1.8 之后,当链表长度超过8时,就会在添加元素的同时将原来的单链表转化为红黑树。利用红黑树可以提高HashMap的性能。

   我们一起看一下HashMap的put方法。

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

   putVal的第四个参数的含义是如果是true不会改变当前的value。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //如果table为null则需要resize
    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;
        //如果节点key存在且key相同则直接覆盖value
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //如果当前的p是红黑树,则直接在树中插入键值对
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        //判断链表长度是否大于8,如果大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作
        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;
}

   我们再来一起看一下HashMap的get方法。

    public V get(Object key) {
    Node<K,V> e;
    //通过getNode去寻找key对应的value,判断是否为null,不是就返回对应的value值。
    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) {
        //如果第一个节点的hash就是那么直接返回
        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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值