Featured image of post HashMap 源码分析

HashMap 源码分析

HashMap

# HashMap 源码分析

# 1 属性

  1. 初始化容量

    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
    
  2. 最大容量

    static final int MAXIMUM_CAPACITY = 1 << 30;
    
  3. 负载因子

    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
  4. 红黑树阈值

    static final int TREEIFY_THRESHOLD = 8;
    
  5. 链表阈值

    static final int UNTREEIFY_THRESHOLD = 6;
    
  6. 红黑树桶阈值

    static final int MIN_TREEIFY_CAPACITY = 64;
    
  7. table 数组,用来初始化

    transient Node<K,V>[] table;
    
  8. entrySet 存放缓存

    transient Set<Map.Entry<K,V>> entrySet;
    
  9. 桶的数量

    transient int size
    
  10. 修改次数

    transient int modCount;
    
  11. 阈值

    int threshold
    
  12. 负载因子

    float loadFactor
    

# 2 构造方法

  1. HashMap()

    public HashMap(){
    	   this.loadFactor = DEFAULT_LOAD_FACTOR;
    }
    
  2. HashMap(int initialCapacity)

    public HashMap(int initialCapacity){
    	   this(int initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    
  3. HashMap(int initialCapacity, float loadFactor )

    public HashMap(int initialCapacity, float loadFactor){
    	   if (initialCapacity < 0){
            //抛出数值异常
        }
        if (initialCapacity > MAXIMUM_CAPACITY){
            initialCapacity = MAXIMUM_CAPACITY;
        }
        if (loadFactor <= 0 || Float.isNaN(loadFactor)){
            //抛出数值异常
        }
        this.loadFactor = loadFactor;
        //tableSizeFor,大于等于当前值的下一个 2 的幂,比如输入 17,返回 32
        this.threshold = tableSizeFor(initialCapacity);
    }
    

# 3 增加元素

  1. put 方法分析

    public V put(K key, V value){
    	   return putVal(hash(key), key, value, false, true);
    }
    
  2. hash 方法分析

    static int hash(Object key){
    	   int h;
    	//key 为空返回 0,先计算 key 的 hashcode,然后与 h 无符号右移 16 位后的二进制进行异或
    	   return key == null ? 0 : (h = key.hashCode() ^ (h >>> 16));
    }
    
  3. putVal 方法分析

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict){
    	   Node<K,V>[] tab;
        Node<K,V> p;
        int n, i;
        /*
        * 如果 table 是否等于空或者等于 0,如果是则进行初始化
        */
        if ((tab = table) == null || (n = tab.length) == 0){
            n = (tab = resize()).length();
        }
        /*
        * 哈希取模,i = (n - 1) & hash,对值的位置进行确定
        * 也是 capacity 为 2 的幂的原因,与运算效率高于%
        * capacity 为 2 的幂次时,(n - 1) & hash = hash % n
        * 如果 tab[i] = null,新增一个元素
        */
        if ((p = tab[i = (n - 1) & hash]) == null){
            tab[i] = newNode(hash, key, value, null);
        } else {
            //说明该位置有值了
            Node<K,V> e;
            K k;
            if (p.hash == hash && ((k = p.key) == key ||
                                   (key != null && key.eqauls(k)))){
                //key 值存在,无论链表还是红黑树都需要替换
                e = p;
            } else if (p instanceof TreeNode){
                //如果是红黑树
                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);
                        //大于红黑树阈值,转换红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1){
                            treeifyBin(tab, hash);
                        }
                        break;
                    }
                    if (e.hash && hahs && ((k = e.key) == key ||
                                           (key != null && key.equals(k)))){
                        break;
                    }
                    p = e;
                }
            }
            /*
            * 如果链表中重复就直接替换
            */
            if (e != null){
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null){
                    e.value = value;
                }
                afterNodeAccess(e);
                return oldValue;
            }
        }
        //记录修改次数
        modCount++;
        //如果超过 threshold,调用 resize
        if (++size > threshold){
            resize();
        }
        afterNodeInsertion(evict);
        return null;
    }
    
    • 如果定位到的数组位置没有元素,直接插入。
    • 如果定位到的数组位置有元素,就要和插入的 key 比较,key 相同则直接覆盖,如果不相同,则判断 p 是否是 TreeNode,如果是则调用 e=((TreeNode<K,V)p).putTreeVal(this, tab, hash, key, value)将元素添加进入。如果不是则遍历链表插入到链表尾部。
  4. resize 方法分析

    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){
                //threshold 加倍
                newThr = oldThr << 1;
            }
        } else if (oldThr > 0){
            newCap = oldThr;
        } else {
            //默认 Capacity 和 threshold,分别为 16 和 12
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0){
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUN_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                     (int) ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null){
            /*
            * 省略,拷贝旧的 hash 桶到 newTab
            */
        }
    }
    

# 4 读取元素

  1. get 方法分析

    public V get(Object key){
    	Node<K,V> e;
    	return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    
  2. getNode 方法分析

    final Node<K,V> getNode(int hash, Object key){
    	Node<K,V>[] tab;
    	Node<K,V> first, e;
    	int n;
    	K k;
    	//table 有元素
        if ((tab = table) != null && (n = tab.length) > 0 &&
           (first = tab[(n - 1) & hash]) != null){
            //从第一个 node 开始
            if (first.hash = hash && ((k = first.key) == key ||
                                     (key != null && key.equals(k)))){
                return first;
            }
            //first 的下一个 node
            if ((e = first.next) != null){
                //若是红黑树,调用红黑树查找方法
                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);
            }
        }
        //table 没元素了,直接返回 null
        return null;
    }
    

# 5 删除元素

  1. remove 方法分析

    public V remove(Object key){
    	Node<K,V> e;
    	return (e = removeNode(hash(key), key, null, false, true)) == null ?
    		null : e.value;
    }
    
  2. removeNode 方法分析

    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;
        if ((tab = table) != null && (n = tab.length) > 0 &&
           (p = tab[index = (n - 1) & hash]) != null){
            Node<K,V> node = null;
            Node<K,V> e;
            K k;
            V v;
            if (p.hash == hash && ((k = p.key) == key ||
                                   (key != null && key.equals(k)))){
                node = p;
            } else if ((e = p.next) != null){
                //如果是红黑树,调用红黑树查找方法
                if (p instanceof TreeNode){
                    node = ((TreeNode<K,V>)p).getTreeNode(hash,key);
                } else {
                    //否则,迭代链表
                    do{
                        if (e.hash == hash && ((k = p.key) == key ||
                                              (key != null && key.equals(k)))){
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            //找到节点了
            if (node != null && (!matchValue || (v = node.value) == value ||
                                (value != null && value.equals(v)))){
                //调用红黑树删除节点的方法
                if (node instanceof TreeNode){
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                } else if (node == p){
                    //是链表头部
                    tab[index] = node.next;
                } else {
                    p.next = node.next;
                }
                //修改次数
                modCount++;
                size--;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }
    

# 6 底层数据结构分析

# 6.1 JDK1.8 之前

  • 底层:数组加链表

  • 基本原理:通过 key 的 hashcode 经过扰动处理得到 hash 值,然后通过(n - 1) & hash 判断当前元素存放的位置,如果当前位置存在元素的话,就判断该元素与要存放的元素的 hash 值以及 key 是否相同,如果相同则直接覆盖,不相同就用拉链法解决冲突。

  • 扰动函数:hash 方法,目的是防止一些实现比较差的 hashcode 方法,减少碰撞。

  • hash 方法:

    static int hash(int h){
    	h ^= (h >>> 20) ^ (h >>> 12);
    	return h ^ (h >>> 7) ^ (h >>> 4);
    }
    

    性能较于 1.8 差,扰动次数为 4

  • 拉链法:将链表和数组结合,也就是创建一个链表数组 Node<K,V>[],如果遇到哈希冲突,则将冲突的值加到链表中即可。

# 6.2 JDK1.8 之后

  • 底层:数组加链表加红黑树
  • 基本原理:当链表长度大于阈值 8 时,会调用 treeifyBin 方法,根据 HashMap 数组决定是否转化成红黑树,只有当数组长度大于或者等于 64时,才会执行转换红黑树的操作,减减少搜索时间。否则只会进行 resize()方法对数组进行扩容。
Licensed under CC BY-NC-SA 4.0
本博客已稳定运行
总访客数: Loading
总访问量: Loading
发表了 73 篇文章 · 总计 323.75k

使用 Hugo 构建
主题 StackJimmy 设计
基于 v3.27.0 分支版本修改