1.底层结构
- jdk1.7 数组 + 链表
- jdk1.8 数组 + 链表/红黑树
- 如果同一个链表中的节点数量大于8(链表中出现多个节点的概率服从泊松分布,出现8个节点的概率为0.00000006,出现更多节点的概率小于千万分之一),那么该链表就会转变为红黑树结构(平衡二叉树)(转变概率极小,树节点的大小是普通节点的两倍)
2.扩容
- 初始容量为16(根据经验得出来的值,太小容易触发扩容,扩容影响效率,太大浪费空间)
- 按2的倍数扩容(为什么?)
答:HashMap会根据key值计算hash值再计算出在数组中index,是非顺序放入的。假如当容量为16时,我们向其中put键值对,那么这些键值对就必须均匀放入当前的数组中使空间得到最大利用以及尽可能少的使多个键值对计算出同一个索引。故而这个计算index的方法必须是可靠的,index = (n - 1) & hash ,n是数组的长度即HashMap大小,使用二进制表示,hash为key计算出的hash值,那么我们可以发现当n为2的倍数时 n - 1 始终为 1111… ,此时 (n - 1) & hash 的值就等于hash的低位值,这样就使得计算出的index既在上限之内又服从均匀分布。当n不为2 的倍数时,n - 1 的结果就会出现 0 ,在经过 (n - 1) & hash计算后就破坏了hash值的均匀性,甚至可能导致某个index永远不会出现 - 最大容量为 1 << 30
- 扩容界限为 大于 当前容量×负载因子(0.75),即第一次自动扩容发生在size = 13时
3 . 其他注意点
-
HashMap在多线程下不安全,比如两个线程在在同一个桶中执行put操作
-
哈希碰撞:HashMap会根据key计算hash值,然后计算出在数组中的索引,然后将该键值对放入数组中。然而,不同的key可能会计算出相同的哈希值,他们就会被放入相同的索引对应的子集(这个子集就称为哈希桶)中形成链式结构,这时候就发生了哈希碰撞。
-
HashMap的大小变大后不会因为元素的减少而变小
-
当扩容时,桶中元素个数小于6,就会把树形的桶元素 还原为链表结构
-
jdk1.7 中HashMap有死循环问题,jdk1.8中解决了该问题(头插法 -> 尾插法)
-
传统HashMap中当单个桶中节点个数过多时,查找时间复杂度趋近O(n),会失去优势。为了解决这个问题,jdk1.8中引入红黑树(查找复杂度为O(logn))来优化