zoukankan      html  css  js  c++  java
  • 【数据结构学习】关于HashMap的那些事儿

    HashMap UML

    涉及数据结构

    • 红黑树
    • 链表
    • 哈希

    从CRUD说起

    预热知识:
    DEFAULT_INITIAL_CAPACITY = 1 << 4, HashMap默认容量为16(n << m意思是n*2^m)
    MAXIMUM_CAPACITY = 1 << 30最大容量,2^30即10,7374,1824.
    DEFAULT_LOAD_FACTOR = 0.75f负载因子0.75,扩容时需要用到

    TREEIFY_THRESHOLD = 8

    The table, initialized on first use, and resized as necessary. When allocated, length is always a power of two. (We also tolerate length zero in some operations to allow bootstrapping mechanics that are currently not needed.)
    首次使用时初始化,必要时进行调整。调整之后的容量通常为2的次幂。(在一些操作中也允许长度为零,以允许当前不需要的自举机制)

    Node<K,V>[] table 存储KV对

    The next size value at which to resize (capacity * load factor).

    threshold:因为牵涉到扩容,而map的扩容(即对table进行扩容操作)不是到了存满了才扩,是以容量*负载因子作为临界点进行扩容的。

    简单讲下Node

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

    看了之后发现什么,这就是个简单的实现了Map.Entry接口的单链表。


    new HashMap()

    有的时候,大家写代码可能会不指定初始大小Map<String, String> map = new HashMap<>();

    运行map.put("test", "zhangsan");的时候,首先运行一次resize(),此时table = new Node[16]

    image-20210531223131703.png

    (p = tab[i = (n - 1) & hash]) == null经过hash与n-1的与运算,找到一个位置(比如4),如果这个位置值为null,那么tab[4]就放上一个Node(图中仅仅以value做区分,但请大家注意,这实际上是Node,有key,有value,有hash,有next,尽管刚开始next是null)

    image-20210531224840604.png

    假如此时接着执行了map.put("test", "liss");会走p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))这个if分支,然后将原来的value替换为liss但正常情况下我们写业务逻辑,都不会这么写的对吧。

    假如继续put("test2", "zhangsan2")按照这样的方式put下去。插入7条数据之后如下

    image-20210531231326537.png

    当执行map.put("test8", "zhangsan8");时,由于test8的hash与15做运算,得到的位置为4,而4这个位置已经有值了。

    for (int binCount = 0; ; ++binCount) {
        if ((e = p.next) == null) {
            // 新的值就挂在上一个的next指针上
            p.next = newNode(hash, key, value, null);
            // 大于等于7的时候,树化
            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;
    }
    

    然后就这样很开心的继续put,直到遇见map.put("test13", "zhangsan13");

    image-20210531232813649.png

    此时,size > threshold(16 * 0.75 = 12),因此进入resize(),长度扩为16 * 2 = 32进入如下逻辑。

    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    // 对于单个Node节点,直接放到原来的位置+n的位置,比如原来在0,则扩容后在16
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    

    如果e.hash在16对应的那个位置是1,那么就放到hiHead那条链表上,位置处于[j + oldCap]处。
    image-20210601000516548.png


    resize()方法

    调整map的大小,实现map初始化或扩展为原有尺寸的二倍。
    HashMap默认大小16,当存储了12个的时候,如果再put,会先将新的值存于原来的map中,然后发现++size > threshold(这里是12),就会进行调整resize。调整的时候,新的table会变成32,threshold变成24,并且需要将原有的值复制进新的table中。

    扩容为原来大小的2倍代码

    int hash(Object key)

    key == null,hash为0;否则,(h = key.hashCode()) ^ (h >>> 16)即计算hashCode,与hashCode右移16位(除以65536得到的商)做异或(同0异1)运算。
    计算单个字符的hash结果就是对应的ASCII码,例如hash('a')=97

    V put(K key, V value)

    put是允许key为null的
    put操作

    get

    image.png

    当你准备好了,机会来临的时候,你才能抓住
  • 相关阅读:
    JavaScript Array filter() 方法
    Object.assign方法的使用入门
    使用ES6新特性async await进行异步处理
    win10系统怎么设置软件开机启动
    【ES6学习笔记之】Object.assign()高级编程
    如何使用闭包形成计数器
    多次调用settimeout 如何使用单例模式
    在线表单设计器现在已经开源
    VisualStudio2017集成GitHub
    PHP使用curl替代file_get_contents
  • 原文地址:https://www.cnblogs.com/studentytj/p/9657313.html
Copyright © 2011-2022 走看看