一起看源码:深入HashMap


简介: 采用数组+链表 或者 数组+红黑树方式进行元素的存储。
存储在 hashMap集合中的元素都将是一个 Map.Entry 的内部接口的实现。
当数组的下标位是链表时,此时存储在该下标位置的内容将是Map.Entry 的一个实现 Node 内部类对象。
当数组的下标位是红黑树时,此时存储在该下标位置的内容将是 Map.Entry 的一个实现 TreeNode 内部类对象。

继承结构

在这里插入图片描述
AbstractMap:继承于 Map 的抽象类,它实现了 Map 中的大部分 API。其它 Map 的实现类 可以通过继承 AbstractMap 来减少重复编码

认识红黑树

说HashMap前,先简单的认识下红黑树的概念:
1.红黑树一种特殊的平衡二叉查找树.。
2.红黑树在数据的插入和删除上比平衡二叉树(AVL)效率更高 。
3.红黑树在数据的查询上由于可能存在树的高度比 AVL 树高一层的情况,查询性能略差一点。

注意:选择红黑树是查询和增删性能的折中选择
红黑树 5大规则:
1.每个节点都用一个标志位来标识或者是黑色,或者是红色。
2.根节点是一定是黑色。
3.每个叶子节点(NIL 节点)是黑色。
4.如果一个节点是红色的,则它的子节点必须是黑色的。
5.从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点

HashMap

HashMap 重要的成员属性:

在这里插入图片描述

说明:
int DEFAULT_INITIAL_CAPACITY = 1 << 4:hashMap的默认数组长度是 1左位移4位就是1*2^4=16

在这里插入图片描述

说明:
float DEFAULT_LOAD_FACTOR = 0.75f:扩容的平衡因子0.75。

在这里插入图片描述

说明:
int TREEIFY_THRESHOLD = 8:链表转换红黑树的元素个数,数组内元素大于8就会由链表转为红黑树

在这里插入图片描述

说明:
int UNTREEIFY_THRESHOLD = 6:红黑树转为链表的元素个数,数组内元素个数到6时就会由红黑树转为链表

在这里插入图片描述

说明:
Node<K,V>[] table:存储元素的数组对象,底层是Entry<K,V>
注意:Node<K,V>是HashMap中的静态内部类,实现Map类中的Entry<K,V>接口

在这里插入图片描述

说明:‘
size:集合中元素的个数
modCount:用于FailFast机制,类似于版本号(详见文章:深入List 分支 ArrayList)

put(K key, V value)过程

1.找到put(K key, V value)方法
在这里插入图片描述
2.找到hash(Object key)方法,传入key
这方法就是将key进行了hash运算并返回

注意:
key==null时默认为0,这也就是为什么hashMap可以将null作为key来存储
key!=null时就将key的hashcode值进行高低十六位的异或运算

在这里插入图片描述
3.进入putVal方法:
这里主要就看源码中添加元素的key不重复的情况:
注意:注释是我自己手写的,代码为源代码

 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)  //table的定义上边已经介绍到了,存储元素的数组对象
            n = (tab = resize()).length; //这里就是初始化容量的地方,第一次存储元素时,数组肯定为null或者长度为0。
        if ((p = tab[i = (n - 1) & hash]) == null) //存储下标确立的地方,
            tab[i] = newNode(hash, key, value, null); //该下标位没有元素时(也就是key不重复时),存储元素的地方
        else {
        //这里就是key重复的情况,省略。。。
         }
        ++modCount;
        if (++size > threshold) //当前集合中元素的个数+1 > 扩容的阈值时进行扩容(threshold在resize()中给予赋值:当前数组长度*0.75)
            resize(); //动态扩容的地方
        afterNodeInsertion(evict);
        return null;

存储下标确立过程

上边说到了存储下标确立的地方,那么过程具体是什么呢?

说明:
n:看上边中n = (tab = resize()).length;其实就是初始化数组的长度也就是16(这里假设为第一次添加元素)
hash:就是传入的hash值,也就是int hash(Object key)方法将key经过hash计算得出得值
假设传入得key=“czy”,经hash(Object key)方法计算返回得hash值为99043
i = (n - 1) & hash为(16-1) & 99043 = 3即i=3
那么该元素得下标即为3

> 顺带说一下与运算,以上述为例: 
> 15 & 99043 将其转换为二进制即为1111	& 1 1000 0010 1110 0011> 计算方式:两位同时为“1”,结果才为“1”,否则为0
> 1 1000 0010 1110 0011> 0 0000 0000 0000 1111
> ---------------------------------
> 0 0000 0000 0000 0011
> 计算结果为:0011转换为十进制为3

在这里插入图片描述

HashMap的初始化与动态扩容

HashMap的初始化与动态扩容都在resize()这个方法,这里只看初始化与动态扩容相关的代码,代码中的注释都是手写,其他代码省略。

注意:
初始化在第一次进行添加元素时。
动态扩容:比如说当前的容器容量是16,负载因子是0.75。16*0.75=12,也就是说,当集合中元素数量达到了12的时候就会进行扩容操作。
这里的扩容操作假设为第一次扩容

 final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;  //将原来的数组对象赋值给oldTab,初始化时table为null即oldTab为null。
        int oldCap = (oldTab == null) ? 0 : oldTab.length; //初始化时oldCap肯定为0,否则为数组的长度
        int oldThr = threshold;//扩容的阈值赋值给oldThr,这里假设第一次扩容,阈值为12(16*0.75)
        int newCap, newThr = 0;
        if (oldCap > 0) { //进行扩容的操作
            if (oldCap >= MAXIMUM_CAPACITY) { //int MAXIMUM_CAPACITY 定义为1 << 30,数组长度怎么可能大于1*2^30,所以说基本上是进不到这个if语句的
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //那么来到这里,假设为第一次扩容oldCap = 16:newCap= 16 << 1 = 32 < 1*2^30  && 16 >= 16(DEFAULT_INITIAL_CAPACITY),条件满足,进入if语句:newThr  = oldThr << 1 即 12 << 1 = 24
            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; //newCap = 16;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //newThr = 0.75 * 16 = 12
        }
		threshold = newThr; //threshold = 12即第一次扩容的阈值为12
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // 创建一个数组对象 长度为newCap
        table = newTab; //将新建的数组对象赋值给table
		if (oldTab != null) {
			//此处省略的操作为:
			//如果集合中有数据时,进行扩容后的操作:
			//判断元素数据结构时红黑树还是链表,并根据对应的操作将原来的元素添加到新的数组中去,并对下标进行处理
		}
 	return newTab;
 }

理解get(Object key)的过程

该方法通过传入的key找出对应的值

1.找到get(Object key)方法
hash(key)这个方法上边已经详细说过,就是根据key计算出一个hash值,直接下一步进入getNode(int hash, Object key)方法
在这里插入图片描述
2.进入getNode(int hash, Object key)方法,传入通过该key计算出的hash值和key值

final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k; 
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {  //如果当前数组不为null,并且数组元素个数>0,将通过确立下标得到的元素赋值给first ,并且first不为null时,进入该if语句
                    if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))//如果first对象中保存的hash值等于通过当前key计算出的hash值并且first对象中保存的key值与用户传入的key值相同时,就进入该if语句,返回first对象
                return first;
            if ((e = first.next) != null) { //走到这就说明hash值不相等,或者key值不相同
                if (first instanceof TreeNode) //如果first对象数据结构是红黑树,就以红黑树的方式获取值节点对象,这里不再深入了。
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                    
                do {//如果程序走到这里,就说明first节点对象的数据结构是链表,就以链表的方式获取节点对象,并返回。
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;//说明没有该key对应的值呗,返回null
    }

3.返回到get(Object key)方法中

return (e = getNode(hash(key), key)) == null ? null : e.value;

如果getNode(int hash, Object key)方法返回的是null 那么get方法就返回null值
如果getNode(int hash, Object key)方法返回的不为null即是一个Node<K,V>节点,那么就返回该节点对象的value值。

发布了21 篇原创文章 · 获赞 34 · 访问量 8630

猜你喜欢

转载自blog.csdn.net/weixin_45240169/article/details/104243920