HashMap JDK1.8

文章目录

        • 构造方法
        • put(K key, V value)方法
        • hash()方法
        • putVal()方法
        • resize()方法
        • get(Object key)方法
        • remove(Object key)方法
        • containsKey(Object key)方法
        • containsValue(Object value)方法

原理:数组 + 链表 + 红黑树

构造方法

static final float DEFAULT_LOAD_FACTOR = 0.75f;	//负载因子默认0.75
//无参构造
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; 	//初始化负载因子
}

put(K key, V value)方法

public V put(K key, V value) {
    //1.调用hash(key)计算key的hash值
    //2.key
    //3.value
    //4.false:  更改HashMap现有的value值
    //5.true:不是创建模式
    return putVal(hash(key), key, value, false, true);
}

hash()方法

static final int hash(Object key) {
    int h;
    //如果key==null,返回0
    //如果key!=null,①获取key的hashCode值,然后②将key的hashCode值进行无符号右移16位,将两者进行异或
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

为什么要右移16位?

为了减少碰撞,进一步降低hash冲突的几率。int类型的数值是4个字节的,右移16位异或可以同时保留高16位于低16位的特征。

为什么要进行异或运算?

首先将高16位无符号右移16位与低十六位做异或运算。如果不这样做,而是直接做&运算那么高十六位所代表的部分特征就可能被丢失 将高十六位无符号右移之后与低十六位做异或运算使得高十六位的特征与低十六位的特征进行了混合得到的新的数值中就高位与低位的信息都被保留了 ,而在这里采用异或运算而不采用& ,| 运算的原因是 异或运算能更好的保留各部分的特征,如果采用&运算计算出来的值会向1靠拢,采用|运算计算出来的值会向0靠拢。


putVal()方法

/**
 * Map.put的实现
 * @param hash key的哈希值 
 * @param key 放入hash表的键
 * @param value 放入hash表的值
 * @param onlyIfAbsent 如果为true,不修改存在的值value
 * @param evict 如果为false,哈希表属于创建模式.
 * @return 返回旧的值,如果没有旧的值返回null
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //如果hash表为空,或者hash表长度==0,则进行扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        //初始化哈希表,进行扩容
        n = (tab = resize()).length;
    
    // n:是hash表的长度,根据hash值和(n-1)计算新增的元素需要放入hash表的下标i
    // 然后判断hash表的[i]位置如果没有元素存在,则新创建一个节点存入该位置
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        // 如果tab[i]位置存在元素p,则需要进一步判断
        Node<K,V> e; K k;
        // 如果该桶的第一元素p和插入的元素key相同,则更新value
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            // 如果p节点是一个树的节点,则将该节点插入到树中
            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);
                    // 插入后判断链表的长度是否大于等于7,如果大于等于,则尝试将链表转为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        // 尝试将链表转为红黑树,如果tab!=null && tab.length>=64,才转为红黑树
                        // 否则只是扩容
                        treeifyBin(tab, hash);
                    break;
                }
                // 如果遍历链表存在key相同的元素,则退出循环 
                // hash表的key是通过hashCode()和equals()来区分的
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 如果hash表存在key相同的元素,则将旧的value值替换为新的value值,然后返回旧的值
        if (e != null) { 
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            // LinkedHashMap的回调函数
            afterNodeAccess(e);
            return oldValue;
        }
    }
    // 记录HashMap的修改次数
    // 作用:为了避免在迭代的过程中不变动HashMap的结构,当HashMap的结构发生变动,则抛出异常
    ++modCount;
    // 如果hash表的实际元素大于阈值,则进行扩容
    if (++size > threshold)
        resize();
    // LinkedHashMap的回调函数
    afterNodeInsertion(evict);
    return null;
}

resize()方法

//如果哈希表为空,则初始化,否则进行扩容
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    //获取旧的哈希表容量
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //获取哈希表旧的实际容量(capacity * load factor),即阈值
    int oldThr = threshold;
    int newCap, newThr = 0;
    //如果哈希表不为空,hashMap已存在,进行扩容,会进入这里的逻辑
    if (oldCap > 0) {
        // 旧容量很大,大于 1<<30,则将旧容量oldCap=Integer.MAX_VALUE
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 旧容量的2倍小于1<<30,并且  旧容量大于等于初始容量16,则将旧阈值扩容为原来的2倍
        // 此处的(newCap = oldCap << 1)就是hash表的扩容为原来的2倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // 2倍阈值
    }
    else if (oldThr > 0) // 如果哈希表旧的实际容量 > 0
        // 如果在创建HashMap时使用的构造方法是传入初始容量的的那个,会进入这里的逻辑
        // 在构造方法中会根据传入的容量initialCapacity,计算threshold,就是计算大于等于		、、 
        // initialCapacity的2次幂,如:
        // initialCapacity == 15   threshold = 16
        // initialCapacity == 16   threshold = 16
        // initialCapacity == 17   threshold = 32
        newCap = oldThr;	//将计算的实际容量赋值为新的容量
    else {   
        // 使用HashMap的空参构造方法创建hashMap对象会走这里的逻辑
        // threshold=0时,会进入这里的逻辑
        
        // 将初始容量newCap = 16
        newCap = DEFAULT_INITIAL_CAPACITY;	
        // 计算新阈值newThr = (capacity * load factor)
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }

    // 如果newThr==0,根据新容量计算newThr
    if (newThr == 0) {
        // 计算新容量的阈值ft
        float ft = (float)newCap * loadFactor;
        // 新容量 < (1<<30) 并且 计算的阈值ft < (1<<30) ? (int)ft : Integer.MAX_VALUE
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    //将 newThr 赋值给 阈值
    threshold = newThr;
 //-----------------------------上面的操作就是确定新容量和新阈值--------------------------------
    
    
    @SuppressWarnings({"rawtypes","unchecked"})
    // 根据新容量newCap,创建新的hash表
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    
    
    // 如果不是初始创建hashmap,而是对旧的hash表进行扩容,下面是扩容的逻辑:
    if (oldTab != null) {
        // 遍历旧的hash表,将旧的hash表的节点移到新的hash表中
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            // hash表的第j个桶有元素存在,将该元素保存在e,然后旧的hash表置为null
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                // 如果hash表的第j个位置只有一个元素,则在新hash表中重新计算e元素的下标,并放入其中
                if (e.next == null)
                // 通过该元素保存的hash值 & (newCap - 1),这样可以将hash值映射在[0,newCap-1]范围内
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    // 如果e是一个树的节点,这个和链表移动差不多,也是将树中的节点分摊,然后放入新的tab
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // 否则e就是链表的第一个节点
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    // 遍历链表,将e处位置的链表,移动到新的hash表
                    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);
                    // 将重新计算的链表放入新的hash表
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

get(Object key)方法

public V get(Object key) {
    Node<K,V> e;
    // 先计算key的hash值,然后调用getNode()方法
    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;
    // 判断hash表不为null,且长度>0,且tab[i]的第一个元素不为null,才进行get操作,否则直接返回null
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        
        // 比较桶的第一个元素,如果相等,则找到,直接返回第一个元素
        if (first.hash == hash && 
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        
        // 如果第一个元素key不相等,如果该桶的第一个元素的下一个元素不为null,则进行遍历
        if ((e = first.next) != null) {
            // 如果是TreeNode,则调用getTreeNode遍历红黑树获取value
            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;
}

remove(Object key)方法

public V remove(Object key) {
    Node<K,V> e;
    // 移除某个元素,如果存在,则返回移除的元素的value,否则返回null
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}
/*
 * @param hash 根据key计算的hash值
 * @param key 移除的key
 * @param value 要匹配的value,没有则忽略
 * @param matchValue 如果为true,则仅当value相等时才移除
 * @param movable 如果为false,则在删除时不要移动其他节点
 * @return 返回移除的节点,或者null
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    // 仅当元素存在才移除,否则直接返回null
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        
        Node<K,V> node = null, e; K k; V v;
        
        // hash表的第一个元素相等,则记录该元素为node
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        // 如果第一个元素不相等,并且存在下一个元素
        else if ((e = p.next) != null) {
            // 如果是TreeNode节点
            if (p instanceof TreeNode)
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            else {
                // 如果是链表哦
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        
        // 上面的代码已经获取了要移除的node,或者为null
        // 如果hash表存在要移除的节点,则进行移除
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            // 如果node是TreeNode节点,进行移除
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            // 如果node是某个桶的第一个元素,则将node的下一个元素作为该桶的第一个元素
            else if (node == p)
                tab[index] = node.next;
            // 如果node是某个桶链表的某个节点,则进行移除,p节点为node节点的前一个节点
            else
                p.next = node.next;
            // 修改次数+1
            ++modCount;
            // 元素-1
            --size;
            afterNodeRemoval(node);
            // 返回移除的节点
            return node;
        }
    }
    return null;
}

containsKey(Object key)方法

public boolean containsKey(Object key) {
    // 底层调用的是get()方法,判断get方法有没有返回值
    return getNode(hash(key), key) != null;
}

containsValue(Object value)方法

public boolean containsValue(Object value) {
    Node<K,V>[] tab; V v;
    // hash表存在
    if ((tab = table) != null && size > 0) {
        // 遍历hash表第一个元素
        for (int i = 0; i < tab.length; ++i) {
            // 从第一个元素开始遍历链表和红黑树
            for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                if ((v = e.value) == value ||
                    (value != null && value.equals(v)))
                    return true;
            }
        }
    }
    return false;
}

你可能感兴趣的:(JavaSE,知识点总结,哈希算法,算法)