- 1. HashMap在jdk1.7跟1.8有什么区别?
- 2. 1.8以后为什么要使用红黑树
- 3. 为什么不一开始就是使用红黑树,而是先是链表再树化
- 4.为什么是超过8,而不是超过其他的?阈值为什么是8?
- 5. 索引是如何计算的?hashCode都有了,为何还要提供hash()方法?
- 6. 数组容量为什么是2的n次幂?
- 7. 红黑树什么时候转换成链表的
- 8. 介绍一下put方法的流程,1.7与1.8有何不同?
- 9. 加载因子默认为什么是0.75F
- 10. 多线程会出现什么问题?
- 11.key是否为null,作为key的对象有什么要求?
- 12. String对象的hashCode()如何设计的,为啥每次乘的31
- 13. HashTable
- 14. ConcurrentHashMap
1. HashMap在jdk1.7跟1.8有什么区别?
1.7 数组加链表的结构
1.8 数组加链表或者红黑树,什么时候是红黑树,当数组长度扩容到64之后,并且相同的
节点下容量>8时(同一个节点下可能会出现>8的情况,TODO//最大为多少)
会出现同一个节点大于8的情况
2. 1.8以后为什么要使用红黑树
因为在1.8之前都是链表结构,如果链表过长的话影响hashMap的性能
3. 为什么不一开始就是使用红黑树,而是先是链表再树化
因为数据量比较小的时候,短的链表结构其实刚开始是比树效率高的
而且红黑树占用的内存比较多,链表数据结构是NODE,而树是TREENODE
TREENODE比NODE的成员变量多很多
4.为什么是超过8,而不是超过其他的?阈值为什么是8?
因为选择8的话树化的几率为亿分之8,选择8就是为了树化的几率足够小
TreeNode占用空间也比普通的=Node的大,如非必要,尽量还是使用链表
5. 索引是如何计算的?hashCode都有了,为何还要提供hash()方法?
- 先进行对象的HashCode,再进行调用HashMap的Hash()方法,最后是&(capacity-1)计算得到索引
jdk 1.7:(移位或者异或操作)
jdk1.8:(移位或者异或操作)
因为一次hash值计算并不能使得数据分布均匀, 防止超长的链表产生
6. 数组容量为什么是2的n次幂?
- 计算索引的时候,如果是2的n次幂的话可以使用位与运算代替取模,效率更高了
- 扩容的时候使用hash&oldCap==0的元素留在原来的位置,否则新的位置= 旧的位置+oldCap;
7. 红黑树什么时候转换成链表的
- 扩容时数量小于等于6时
- remove之前红黑树的左孩子,右孩子,或者左孙子不存在的情况下,会转换成链表
8. 介绍一下put方法的流程,1.7与1.8有何不同?
- HashMap懒得创建数组的,首次使用才创建数组
- 计算索引(计算桶下标)
- 如果桶下标还没人占用,创建Node占位返回
- 如果桶下标已经有人占用
- 已经是TreeNode走红黑树的添加或更新逻辑
- 是普通Node,走链表的添加或者跟新逻辑,如果说链表长度超过树化阈值走树化逻辑
- 返回前检查容量是否超过阈值一旦超过进行扩容
- 1.7 是大于阈值且没有空位时才扩容,而1.8是大于阈值就扩容
- 1.8在进行扩容计算Node索引时,会优化(新的位置会按位与)
9. 加载因子默认为什么是0.75F
- 在空间占用与查询时间之间取得较好的权衡
- 大于这个值,空间节省了,但链表就会比较长影响性能
- 小于这个值,冲突减少了,但扩容就会更频繁,空间占用多
10. 多线程会出现什么问题?
- 扩容死链(1.7)
- 数据错乱(1.7, 1.8)
11.key是否为null,作为key的对象有什么要求?
- hashMap的key可以为null,但Map的其他实现则不然
- 作为key的对象,必须实现hashCode和equals,并且key的内容不能修改(不可变)、
3.null的时候放的位置:
12. String对象的hashCode()如何设计的,为啥每次乘的31
13. HashTable
初始容量是11,每次扩容的话是2n+1,加载因子也是0.75
HashCode值不需要二次hash,因为它的容量不是2的n次幂,扩散性比较好
线程安全的,给整个集合上锁,一次只能由一个线程操作(并发很低)