从注释中,看到了以下关键信息
* permits null values and the null key. (允许空值和空key) * As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs. Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the HashMap class, including get and put). The expected number of entries in the map and its load factor should be taken into account when setting its initial capacity, so as to minimize the number of rehash operations. If the initial capacity is greater than the maximum number of entries divided by the load factor, no rehash operations will ever occur.(负载因子默认值是0.75,是均衡时间和空间算出来的值,较高的值会减小空间开销,但增加查找成本(哈希冲突增加),不扩容的条件:数组容量 > 需要数组的大小 / 负载因子) * 非线程安全,可以自己加锁或者使用Collections.synchronizedMap实现线程安全 * 在迭代过程,如果HashMap结构被修改,会快速失败
另外,大家都知道HashMap是数组+链表+红黑树的数据结构
常见属性
/** static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16初始默认容量16 static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量2^32 static final float DEFAULT_LOAD_FACTOR = 0.75f;//负载因子 static final int TREEIFY_THRESHOLD = 8;//链表长度大于等于8时,链表转成红黑树(转红黑树的必要条件1) static final int UNTREEIFY_THRESHOLD = 6;//红黑树节点小于6时,红黑树转换成链表 static final int MIN_TREEIFY_CAPACITY = 64;//数组容量大于64时,才会转红黑树(转红黑树的必要条件2) transient Node<K,V>[] table;//存放数据的数组 transient Set<Map.Entry<K,V>> entrySet; transient int size;//hashmap实际容量大小,可能因为并发导致并不准确 transient int modCount;//迭代过程中记录结构是否改变,如果改变会fail-fast int threshold;//扩容的门槛,数组大小永远是2的幂次方。
新增
1、空数组是否初始化,没有初始化则初始化
2、如果通过key的hash能够找到值,跳到6,否则3
3、如果hash冲突,拉链(特定情况红黑树)
4、如果是链表,则加到链表末尾
5、如果是红黑树,调用红黑树新增方法
6、通过2、4、5讲元素新增,根据onlyIfAbsent判断是否需要覆盖
7、判断是否需要扩容,需要扩容进行扩容,结束
//入参hash,通过hash函数计算出来的值 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i;//n表示数组长度,i表示数组索引下标,p为i下标位置Node值 if ((tab = table) == null || (n = tab.length) == 0)//判断数组是否为空,如果是就resize初始化 n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null)//判断当前索引位置是否为空,如果是,则直接新建节点在当前位置 tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; //如果key和hash值都相等,则直接赋值(相当于覆盖) if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) 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) // -1 for 1st treeifyBin(tab, hash);//链表长度大于等于8时,转成红黑树 break; } //如果有元素和新增元素相等,结束循环 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //说明新节点的新增位置已经找到 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue;//返回老值 } } //记录hashmap的数据结构发生了变化 ++modCount; //如果实际大小超过扩容门槛,则扩容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
红黑树有五个原则:
1、节点是红色或者黑色。
2、根是黑色。
3、所有叶子结点都是黑色。
4、从任意节点到叶子结点路径包含相同数量的黑色节点。
5、从每个叶子到根的所有路径不能有两个连续的红色节点。
查找
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) { if (first.hash == hash && // 第一个位置特殊处理 ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode)//红黑树查找 return ((TreeNode<K,V>)first).getTreeNode(hash, key); do {//链表查找,e为链表头(第一个非数组的节点) if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null);//没找到拿下一个节点,直到拿到链表末尾或者找到结束 } } return null; }
1、根据hash定位数组索引位置,equals判断当前节点的key是否就是我们要的key
2、判断当前节点是否有next节点,有的话判读是红黑树还是链表,采用不同策略
3、分别走链表和红黑树不同类型的查找方法
红黑树过程太过复杂...就不分析了