• Java的HashMap


    HashMap

    概述

    • HashMap是一个
    • jdk8源码

    构造器

    有4个构造器:

    public HashMap()
    public HashMap(int initialCapacity)
    public HashMap(int initialCapacity, float loadFactor)
    public HashMap(Map<? extends K, ? extends V> m)
    
    • 除了显式设置loadFactor,其他情况都是设置为默认的0.75。
    • 传入另外一个Map需要满足泛型上下界的要求,比如只能由<String,String>转成<Object,Object>。

    主要成员变量

    • Node<K,V>[] table;
      哈希表。
    • Set<Map.Entry<K,V>> entrySet;
      用来遍历的Entry集合。
    • int size;
      map中元素个数。
    • int modCount;
      源码上说的是结构化修改的次数,关系不大。
    • int threshold;
      阈值,当size大于这个阈值,就要resize
    • final float loadFactor;
      加载因子,表示哈希表中元素的密集程度,默认是0.75。

    put

    table

    哈希表,就是一个Node数组,而Node是HashMap里一个静态内部类,实现了Map接口里的Entry接口,可以看出Node类其实是一个链表节点,所以HashMap就是使用了链地址法来解决冲突的。
    Node类重写了hashCode方法,把key和value的哈希值异或起来。

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }
    

    putVal

    put方法内部是调用了putVal方法,putVal(hash(key), key, value, false, true)
    多了三个参数,第一个是哈希值,后两个是onlyIfAbsent和evict。
    onlyIfAbsent是put方法用来判断是否覆盖。
    evict应该是LinkedHashMap用到的,默认都为true。

    • 第一步就是判断哈希表是否为空,若是进行resize(),保证哈希表的长度是2的k次方的形式。
    • 第二步就是通过哈希值计算下标,计算的方法是(n - 1) & hash,因为n=2^k,所以这个操作就是取哈希值的低k位,其实就是取模
    • 第三步判断是否冲突了,若否就直接放一个新节点上去,存有hash值,key和value。
    • 第四步就是解决冲突。

    注意这里的相等比较用到了==和equals(),这是key覆盖的情况。

    if (p.hash == hash &&
        ((k = p.key) == key || (key != null && key.equals(k))))
        e = p;
    

    如果哈希表上不是链表节点,而是平衡树节点,调用putTreeVal方法。

    else if (p instanceof TreeNode)
        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);    
    

    这是哈希位置相同而key不同的清空,也就是哈希冲突。
    遍历对应位置的链表,加到表尾,如果链表中间有相等的key,也是覆盖。
    当同一位置链表节点数大于等于8个时,调用treeifyBin方法进行树化,将链表转化为红黑树。

    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;
            }
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                break;
            p = e;
        }
    }
    

    接下来是处理key覆盖的清空,会返回旧的value。
    put方法的onlyIfAbsent是false,所以无论如何都会覆盖相同key的值。
    putIfAbsent方法的onlyIfAbsent是true,所以只有当oldValue是null的时候,才会覆盖。

    if (e != null) { // existing mapping for key
        V oldValue = e.value;
        if (!onlyIfAbsent || oldValue == null)
            e.value = value;
        afterNodeAccess(e);
        return oldValue;
    }
    

    Test code1

    import java.util.HashMap;
    
    public class LearnHashMap {
        public static void main(String[] args) {
            HashMap<String,String> a=new HashMap<>();
            a.put("abc","FF");
            a.putIfAbsent("abc","FFF");  //不会覆盖,仍然是{"abc":"FF"}
            System.out.println(a.put("abc","kk"));
        }
    }
    

    最后是处理对应哈希表下标没有元素的清空,size要加1,如果大于threshold,就扩容。

    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
    

    tableSizeFor

    这个方法可以返回最近且大于cap的2的次幂,可以用来根据容量capacity来确定阈值threshold。
    源码就不看了,神仙位运算看不懂。

    resize

    每次都是2倍的扩容,MAXIMUM_CAPACITY就是1<<30。

    else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
             oldCap >= DEFAULT_INITIAL_CAPACITY)
        newThr = oldThr << 1; // double threshold
    

    如果容量为0,而阈值大于0,直接让容量为阈值。

    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    

    都为0,则用默认值,默认阈值等于默认容量默认加载因子,即160.75=12。

    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    

    然后就是new一个新的节点数组,大小是原数组的两倍,然后将原数组的链表和红黑树copy过去。
    这一步可以把原本一个节点的链表分成两个。

    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;
    }
    

    treeifyBin

    将链表转红黑树

  • 相关阅读:
    Java 小记 — Spring Boot 的实践与思考
    Docker 小记 — Compose & Swarm
    Linux 小记 — 网络管理
    Docker 小记 — Docker Engine
    Nginx 原理解析和配置摘要
    笔记与随想 — 解决问题
    Mac 小记 — 杂录
    编剧小记 — Contour
    Linux 小记 — Ubuntu 自动化配置
    dotnetcore 自动迁移工具
  • 原文地址:https://www.cnblogs.com/zxcoder/p/12252830.html
Copyright © 2020-2023  润新知