• HashMap JDK11


    HashMap

    HashMap继承自AbstractMap,实现了Map:

    HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>
    

    流程:

    1. 计算hashcode

    2. 高位无符号右移16位以参与异或运算(大多数length一般都小于2^16即小于65536)

    3. 取模:(length-1)& hash(就是低位全1,保留了hashcode的零散性质)

    4. 扩容:数组的长度或扩大到原来的2,4,8倍以上,这样做的原因是,取余的时候很好算

      原hash新增位是1,新索引位置就是加一个旧的容量值,新增位是0,则不变

    1.1 几个重要的成员变量:

    transient int size;				//
    transient int modCount;			        // HashMap被改变的次数
    int threshold;					// 门限值
    final float loadFactor;			        // 装载因子
    
    static class Node<K, V> implements Entry<K, V>{}// 静态内部类,实现链表
    
    transient HashMap.Node<K, V>[] table;		// 实现数组-链表
    transient Set<Entry<K, V>> entrySet;		//
    

    initialCapacity默认为16,loadFactory默认为0.75,容量等于16*0.75=12

    1.2 构造函数:

    HashMap提供了四个构造函数,用于指定门限值和装载因子:

    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
        } else {
            if (initialCapacity > 1073741824) {
                initialCapacity = 1073741824;
            }
    
            if (loadFactor > 0.0F && !Float.isNaN(loadFactor)) {
                this.loadFactor = loadFactor;
                this.threshold = tableSizeFor(initialCapacity);
            } else {
                throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
            }
        }
    }
    

    1.3 Put方法

    索引冲突的几种种情况:

    1. key值相同:进行值覆盖

    2. key值不同,hash相同:链表或者树

    3. key值不同,hash不同,取模后相同(索引位置相同):链表或者树

    public V put(K key, V value) {
        return this.putVal(hash(key), key, value, false, true);
    }
    
    // hash 计算hash值
    static final int hash(Object key) {
        int h;
        // 支持 key = null 的形式,
        // hashcode ^ (h >>> 16)
        // 高位参与运算,大多数length一般都小于2^16即小于65536
        return key == null ? 0 : (h = key.hashCode()) ^ h >>> 16;
    }
    
    // putval
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict){
        HashMap.Node[] tab; // Node类实现了一个链表数组
        					// 数组的长度总是 2的n次方
        
        int n;				// n是tab的长度,第一次put或者中途扩容的时候会重新设置
        if ((tab = this.table) == null || (n = tab.length) == 0) {
            n = (tab = this.resize()).length;
        }
        
        Object p;
        int i;
        // 根据hash计算索引位置,若空则插入一个node
        if ((p = tab[i = (n - 1) & hash]) == null) {
            tab[i] = this.newNode(hash, key, value, (HashMap.Node)null);
        } else { //说明待插入位置存在元素
            Object e;
            Object k;
            
            if(((HashMap.Node)p).hash == hash && 
               ((k = ((HashMap.Node)p).key) == key || 
                 key != null && key.equals(k))){ 
                
                e = p;
            } else if(p instanceof HashMap.TreeNode){ // p是红黑树
                e = ((HashMap.TreeNode)p).putTreeVal(this, tab, hash, key, value);
            } else {
                int binCount = 0;
                
                 while(true) { 
                     // 遍历这个节点,如果 binCount > 7 转为红黑树,
                     // code
                     break;
                     // 否则接在列表后面
                     // code
                     break;
                     
                     ++binCount;
                 }
            }
            
            if (e != null) { 
            	// 修改原值~
            }
        }
    }
    

    Node内部类:像是一个链表的形式

    static class Node<K, V> implements Entry<K, V> {
        final int hash;
        final K key;
        V value;
        Node<K, V> next;
    }
    

    1.4 get方法:

    public V get(Object key) {
        HashMap.Node e;
        // 是根据输入节点的 hash 值和 key 值利用getNode 方法进行查找
        return (e = this.getNode(hash(key), key)) == null ? null : e.value;
    }
    
    final HashMap.Node<K, V> getNode(int hash, Object key) {
        HashMap.Node[] tab;
        HashMap.Node first;
        int n;
        // tab非空,长度>0,索引处非空
        if ((tab = this.table) != null && (n = tab.length) > 0 && (first = tab[n - 1 & hash]) != null) {
            Object k;
            // 检查头节点
            if (first.hash == hash && ((k = first.key) == key || key != null && key.equals(k))) {
                return first;
            }
    
            HashMap.Node e;
            if ((e = first.next) != null) {
                
                // 若定位到的节点是 TreeNode 节点,则在树中进行查找
                if (first instanceof HashMap.TreeNode) {
                    return ((HashMap.TreeNode)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;
    }
    

    1.5 Resize方法

    我们在扩容的时候,一般是把长度扩为原来2倍,所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。(左边为0不移动,左边为1移动)

    0000 0000 0000 0000 0000 0000 0000 1111	     n = 16       
    1111 1111 1111 1111 0000 1111 0000 0101                0101		5
    1111 1111 1111 1111 0000 1111 0001 0101                0101		5
    
    0000 0000 0000 0000 0000 0000 0001 1111	     n = 32
    1111 1111 1111 1111 0000 1111 0000 0101               00101		5
    1111 1111 1111 1111 0000 1111 0001 0101               10101		5 + 16
    

    因此,我们在扩充HashMap的时候,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”

    final HashMap.Node<K, V>[] resize() {
        HashMap.Node<K, V>[] oldTab = this.table; 			// 把旧的表装好
        int oldCap = oldTab == null ? 0 : oldTab.length;	        // 计算旧表的容量
        int oldThr = this.threshold;				// 计算旧表门限
        int newThr = 0;
        int newCap;
        if (oldCap > 0) {						// 原表非空的情况
            if (oldCap >= 1073741824) {			        // 超出去就不扩了
                this.threshold = 2147483647;
                return oldTab;
            }
    
            if ((newCap = oldCap << 1) < 1073741824 && oldCap >= 16) {
                newThr = oldThr << 1;
            }
        } else if (oldThr > 0) {				        // 原表是空(其它三个构造函数)
            newCap = oldThr;					// 容量设定为门限
        } else {
            newCap = 16;						// 空参构造函数创建的
            newThr = 12;
        }
        
        if (newThr == 0) {						// 设定门限
            float ft = (float)newCap * this.loadFactor;
            newThr = newCap < 1073741824 && ft < 1.07374182E9F ? (int)ft : 2147483647;
        }
        
        this.threshold = newThr;				        // 开始创建新表
        HashMap.Node<K, V>[] newTab = new HashMap.Node[newCap];
        this.table = newTab;
        if (oldTab != null) {
            for(int j = 0; j < oldCap; ++j) {
                // rehash开始
                HashMap.Node e;
                if ((e = oldTab[j]) != null) {
                    // 原表置空
                    oldTab[j] = null;
                    if (e.next == null) {
                        // 原节点是单点,用它的hash值,重新计算索引
                        newTab[e.hash & newCap - 1] = e;
                    } else if (e instanceof HashMap.TreeNode) {
                        // 红黑树rehash
                        ((HashMap.TreeNode)e).split(this, newTab, j, oldCap);
                    } else {
                        // 链表rehash
                        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) {		// 扩2倍对应的值是0
                                if (loTail == null) {
                                    loHead = e;
                                } else {
                                    loTail.next = e;
                                }
    
                                loTail = e;				// loTail = loTail.next
                            } else {
                                if (hiTail == null) {
                                    hiHead = e;
                                } else {
                                    hiTail.next = e;
                                }
    
                                hiTail = e;
                            }
                            e = next;				// 遍历链表操作
                        } while(next != null);
                        
                        if (loTail != null) {			// 分别移动到新数组上
                            loTail.next = null;			// 低位直接复制
                            newTab[j] = loHead;
                        }
    
                        if (hiTail != null) {			// 高位 原索引+原容量
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        
        return newTab;
    }
    
  • 相关阅读:
    未来可以预测吗?
    电脑浏览器不能连接百度或者其他的网站,提示“此网站无法提供安全连接”的解决方法
    6.CFileDialog的文件过滤器lpszFilter写法 windows编程
    5.返回值IDCANCEL和CommDlgExtendedError函数 windows编程
    2.C++标准库函数:getline函数 定界流输入截取函数 windows编程
    4.使用CFileDialog打开文件对话框,获得文件路径 windows编程
    1.windows编程入门MessageBox使用 windows编程
    3.C++逐行读取txt文件数据,利用getline windows编程
    8.一个超级直观的windows消息队列运行图 windows编程
    7.CFileDialog的5个读取文件信息的函数 windows编程
  • 原文地址:https://www.cnblogs.com/punnpkin/p/13583449.html
Copyright © 2020-2023  润新知