• 常用集合之HashMap


    综述

    数据结构:数组+链表(数组长度小于64会优先考虑扩容,数组长度大于等于64且链表长度大于8会转换为红黑树

    1. HashMap中存储的元素

    	 // 用来存放元素的数组,HashMap的主干 
    	 transient Node<K,V>[] table;
    
    	// HashMap中的元素
    	static class Node<K,V> implements Map.Entry<K,V> {
            final int hash; // key hash后的值
            final K key; // map中的key
            V value; // map中的value
            Node<K,V> next; // hash冲突创建链表时 next才会有值
            
            // 链表中的元素节点
            Node(int hash, K key, V value, Node<K,V> next) {
                this.hash = hash;
                this.key = key;
                this.value = value;
                this.next = next;
            }
      }
    
    // 红黑树中的元素
     static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
            TreeNode<K,V> parent;  // red-black tree links
            TreeNode<K,V> left;
            TreeNode<K,V> right;
            TreeNode<K,V> prev;    // needed to unlink next upon deletion
            boolean red;
    }
    	
    	// 盛放主干数组上的元素
        transient Node<K,V>[] table;
        
    	// 默认大小为16 << 位移运算
        static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    
       // 最大容量2的4次放
        static final int MAXIMUM_CAPACITY = 1 << 30;
    
        // 默认加载因子
        // 为什么要存在加载因子。
        //  加载因子 = 数组中元素个数/数组长度
        static final float DEFAULT_LOAD_FACTOR = 0.75f;
        
    	// 链表长度大于8时会转换为红黑树
        static final int TREEIFY_THRESHOLD = 8;
    
        // 红黑树节点个数小于6时,会退化为链表
        static final int UNTREEIFY_THRESHOLD = 6;
        
        // 数组长度大于等于64时才会转换为链表
        static final int MIN_TREEIFY_CAPACITY = 64;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    2. 创建HashMap

    • 无参构造器: 利用无参构造器创建对象时,默认数组大小为16;
    • 有参构造器: 如果业务场景中知道自己的元素个数,创建元素时尽量指定大小,避免空间的浪费和扩容操作。
      元素个数 = 元素个数 / 0.75 + 1;

    利用无参构造器创建

       /**
         *  默认数组的大小为16,加载因子为0.75
         */
        public HashMap() {
            this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3. put 元素

    
    // 如果key为null, 那么key就不会被散列扰动,hash直接为0
     static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
    
    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;
             
             // 1. 计算元素在数组中的索引位置
             	// i = (n - 1) & hash]
             	// key的hash值和数组的最大索引做hash,得到元素在数组中的下标index
            // 2. put元素,先判断索引为i的位置是否存在元素 	
            if ((p = tab[i = (n - 1) & hash]) == null)
            	// 2.1 数组下标i处不存在元素,hash没有冲突 新建一个元素放在i处,到此结束
                tab[i] = newNode(hash, key, value, null);
    		
    		// 3. 如果经过2步骤的计算,当前下标i处已经有元素
            else {
                Node<K,V> e; K k;
                // 3.1 判断是否是重复,重复的话直接覆盖
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                 // 3.2 3.1中不重复的话,判断是否是树结点,是的话就put
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                   
                 // 3.3 如果3.2中判断不是树结点,就新建一个普通的node 
                else {
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            // 3.4 node创建后判断当前二叉树的节点数是否大于最小结点6,是的话就继续构建红黑树
                            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;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    7. resize 扩容

    扩容为原来的2倍

    // newSize = 扩容为原来的2倍
    // 1. 如果newSize < 最大容量 并且旧的容量>默认值16的话;newSize = 2*旧容量
    if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1;
     // 2. 1不满足的时候判断2是否满足,2满足用2
      else if (oldThr > 0) 
                newCap = oldThr;
     
     //3. 2不满足的话就用默认的
     else {    
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    8. get 元素

    其实和put元素时的逻辑一样

    public V get(Object key) {
            Node<K,V> e;
            // hash(key);先将key进行hash得到一个值
            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;
            // 1. 确定当前存放元素的数组实际存在
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
    			
    			// 2. 校验数组索引下标处的hash当前hash相同,并且索引处的对象key和入参的key是同一个,就返回
                if (first.hash == hash &&  ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                if ((e = first.next) != null) {
                	// 3. 如果第2步不满足,看first节点是否是树结点,是的话就从树结点中获取code
                    if (first instanceof TreeNode)
                        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
    			// 4. 如果第3步不满足的话,就从链表中获取元素
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            return e;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    9. 为什么要有加载因子

    DEFAULT_LOAD_FACTOR = 0.75;

    • 当数组中元素的个数达到数组总长度的0.75倍后,会触发扩容操作;
      因为在hash的过程中数组中空元素的个数越多hash冲突的概率就越大,
    • 但是如果为了避免hash冲突把数组初始化的越大,对空间的浪费就越大
    • 为了不浪费空间,又为了尽可能少的减少hash冲突,0.75是试验后的最佳选择
    
     final void treeifyBin(Node<K,V>[] tab, int hash) {
            int n, index; Node<K,V> e;
            if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                resize();
            else if ((e = tab[index = (n - 1) & hash]) != null) {
                TreeNode<K,V> hd = null, tl = null;
                do {
                    TreeNode<K,V> p = replacementTreeNode(e, null);
                    if (tl == null)
                        hd = p;
                    else {
                        p.prev = tl;
                        tl.next = p;
                    }
                    tl = p;
                } while ((e = e.next) != null);
                if ((tab[index] = hd) != null)
                    hd.treeify(tab);
            }
        }
    
    
    public V remove(Object key) {
            Node<K,V> e;
            return (e = removeNode(hash(key), key, null, false, true)) == null ?
                null : e.value;
        }
     public void clear() {
            Node<K,V>[] tab;
            modCount++;
            if ((tab = table) != null && size > 0) {
                size = 0;
                for (int i = 0; i < tab.length; ++i)
                    tab[i] = null;
            }
        }
    
    
    final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int oldThr = threshold;
            int newCap, newThr = 0;
            if (oldCap > 0) {
                if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1; // double threshold
            }
            else if (oldThr > 0) // initial capacity was placed in threshold
                newCap = oldThr;
            else {               // zero initial threshold signifies using defaults
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
            if (newThr == 0) {
                float ft = (float)newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                          (int)ft : Integer.MAX_VALUE);
            }
            threshold = newThr;
            @SuppressWarnings({"rawtypes","unchecked"})
                Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            table = newTab;
            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)
                            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;
                            }
                        }
                    }
                }
            }
            return newTab;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
  • 相关阅读:
    Python 基础记录 - 第4课
    金山办公:订阅为王?
    ArcGIS解决栅格边界黑边
    @AspectJ注解配置切面编程(注解方式)
    四件不能对宝宝做的事,提早知道预防
    关于前端开发的起源,架构,变迁
    pytorch环境搭建到pycharm项目映射配置(成功后回顾性记录/自用)
    使用LIME解释CNN
    linux内核——进程
    Sprinig Boot优雅实现接口幂等性
  • 原文地址:https://blog.csdn.net/github_39936816/article/details/126493371