HashMap总结:
1、HashMap底层是用数组+双向链表+红黑树实现的
2、插入元素的时候,首先通过一个hash方法计算得到key的哈希值,进而计算出待插入的位置
3、如果该位置为空,则直接插入(包装成Node)
4、如果该位置有值,则依次遍历。比较的规则是,“hash值相同,key值相等”的元素视为相同,则用新值替换旧值并返回旧值。
5、如果该位置的元素是红黑树结构,则同理,查找,找到则替换,没找到则插入。
划重点:
JDK1.8中HashMap与JDK1.7中有很多地方不一样
1、1.8中引入了红黑树,而1.7中没有
2、1.8中元素是插在链表的尾部,而1.7中新元素是插在链表的头部
3、扩容(就是将旧数组的元素移动到新数组)的时候,1.8中不会出现死循环,而1.7中容易出现死循环,而且链表不会倒置
比较点 | HashMap | HashTable | ArrayList |
---|---|---|---|
实现原理 | 见上一节 | 和HashMap原理几乎一样 | |
key和value | 允许key和value为null | 不允许key和value为null | |
扩容策略 | 2倍扩容(oldThr << 1) | 2倍+1扩容(oldCapacity << 1)+1 | 1.5倍oldCapacity + (oldCapacity >> 1) |
安全性 | 线程不安全 | 线程安全 | 线程不安全 |
Hashtable线程安全的策略实现代价很大,get/put所有相关操作都是synchronized的,在竞争激烈的并发场景中性能非常差。
HashMap扩容机制 --- resize()
当hashmap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对hashmap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,所以这是一个通用的操作,很多人对它的性能表示过怀疑,不过想想我们的“均摊”原理,就释然了,而在hashmap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组;
什么时候resize()
当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值—即当前数组的长度乘以加载因子的值的时候,就要自动扩容。