Map
HashMap
hashCode 和 equals 的一些基本约定
Hashtable
TreeMap
PriorityQueue
LinkedHashMap 和 TreeMap
HashMap源码分析
一、HashMap 内部实现基本点分析
HashMap可以看作是数组(Node<K,V>[] table)和链表结合组成的复合结构。
HashMap中的数组被分为一个个桶(bucket)。
通过哈希值决定了键值对在这个数组的寻址。
哈希值相同的键值对,则以链表形式存储
如果链表大小超过阈值(TREEIFY_THRESHOLD, 8),链表就会被改造为树形结构。
门限值
resize 方法
二、容量(capacity)和负载系数(load factor)
容量和负载系数决定了可用的桶的数量
空桶太多会浪费空间,但是如果使用的太满则会严重影响操作的性能;极端情况下,假设只有一个桶,那么它就退化成了链表,完全不能提供所谓常数时间存的性能。
预先设置的容量需要满足,大于“预估元素数量 / 负载因子”,同时它是 2 的幂数。
对于负载因子
如果没有特别需求,不要轻易进行更改,因为 JDK 自身的默认负载因子是非常符合通用场景的需求的。
如果确实需要调整,建议不要设置超过 0.75 的数值,因为会显著增加冲突,降低 HashMap 的性能。
如果使用太小的负载因子,按照上面的公式,预设容量值也进行调整,否则可能会导致更加频繁的扩容,增加无谓的开销,本身访问性能也会受影响
三、树化
树化改造,对应逻辑主要在 putVal 和 treeifyBin 中。
树化改造的逻辑:
当 bin 的数量大于 TREEIFY_THRESHOLD 时,
如果容量小于 MIN_TREEIFY_CAPACITY,只会进行简单的扩容;
如果容量大于 MIN_TREEIFY_CAPACITY ,则会进行树化改造。
为什么 HashMap 要树化?
本质上这是个安全问题,在元素放置过程中,如果一个对象哈希冲突,都被放置到同一个桶里,则会形成一个链表,我们知道链表查询是线性的,会严重影响存取的性能。
构造哈希冲突的数据并不是非常复杂的事情,恶意代码就可以利用这些数据大量与服务器端交互,导致服务器端 CPU 大量占用。这就构成了哈希碰撞拒绝服务攻击,国内一线互联网公司就发生过类似攻击事件。