HashMap实现思路(小白科普)

Android 数据结构 java 小白科普

HashMap是java和Android中常用的一个容器,采用了数组+链表的结构来存储数据(PS:jdk1.8新增红黑树,当链表长度大于8以后,链表会进化成红黑树)。下面具体分析HashMap的实现思路。

1 为什么要用链表

很多人疑惑,实现HashMap直接用数组不就可以了吗,通过hash函数计算出key对应的数组的下标,value直接存进去。为什么会用链表呢?
问题的关键就出在hash函数身上,因为到现在为止还没有出现一种一个不同的key对应的hash值都不一样的函数,(包括MD5,SHA等算法),假如a,b两个key通过hash函数得到的数组下标都是1,a已经存进数组下标为1的位置,b该怎么存储呢?把a对应的value删了,替换成b对应的value?这种作法显然是不可取的。所以在HashMap中引入了链表,还是a,b两个key,存储a的时候,我们先判断下数组下标为1的位置是否有数据,如果没有直接插入数组中,这时候a对应的value插入到了数组中,这时候我们来存储b,我们发现下标为1的位置已经存了数据,我们用a.next来指向b,形成一个链表。以此类推。
《HashMap实现思路(小白科普)》

2 代码实现

1 put方法

 public V put(K key, V value) {
        //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;
        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相等
            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;
                    }
                    //key相等
                    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;
    }

2 get方法

 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) {//取出数组中对应下标数据
            //key相同 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 {
                //循环遍历 找到key相同的对象
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
}

3 总结

HashMap通过数组加链表的方式,实现了时间复杂度为O(1)的快速插入,查询等操作,在平时使用中我们还可以通过指定数组大小来进一步加快它的性能。
文章中只是写了大概的思路,有不对的还请见谅!

    原文作者:算法小白
    原文地址: https://segmentfault.com/a/1190000017233245
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞