hashMap的底层存储结构是?
在1.7是由数组+链表组成,1.8是数组+链表+红黑数
为什么要进行树化?
链表取元素要从头遍历到对应的节点,时间复杂度是O(n),而红黑树基于二叉树时间复杂度是O(logN),从而提高搜索效率。但是红黑树节点的大小约是普通节点的两倍,只有包含足够节点时进行转化才比较合适,这是出于空间与时间平衡的考虑
为什么树化阈值是8?
如果hashCode分布离散足够好的话,一般很少会进行树化,在理想情况下,链表长度符合泊松分布,各长度的命中概率递减,而8是的概率就已经足够小了
为什么退化为链表的阈值是6?
因为如果阈值是7,那么删除一个节点就要退化,增加一个节点就要树化,频繁的转变降低性能,所以才不设置太临界
为什么hashMap的扩容要是二倍扩容?
因为路由算法是 (length-1) & hash(key)
扩容必须二倍是为了维持容量始终为2的幂次方,这样在进行hash散列的时候,因为length - 1
的后几位都是1,在与hash做&运算时得到的结果可能为1也可能为0,增加了散列的不确定性,减少hash碰撞
HashMap为什么线程不安全?
1.7的线程不安全是在扩容方法可能产生死循环,1.8的线程不安全是在put方法,当两个元素hash碰撞时可能导致数据被覆盖并且在扩容之前的判断++size > threshold
并不是一个原子操作
jdk1.7的HashMap为什么会导致死循环?
在并发环境下,1.7的扩容方法是采用头插法将元素迁移到新数组中,由于头插法会导致链表翻转,从而照成两个节点的next相互指向导致死循环
hashcode是唯一的吗?插入元素的时候怎么比较的?
不是唯一的,当桶中不存在元素时直接放入,如果存在则比较桶内元素的Key是否与当前节点相同,相同则直接更新,不同则判断是否是红黑树节点,如果是进行红黑树节点的处理,如果不是则代表是链表节点,从头节点开始比较,如果存在Key相同的则更新,如果直到尾结点还没匹配,则将当前节点作为尾结点插入
HashMap跟HashTable,ConcurrentHashMap有什么区别?
HashMap线程不安全,允许null键null值
HashTable线程安全,不允许null键null值,所有方法都是用synchronized修饰
ConcurrentHashMap线程安全,不允许null键null值,1.7采用分段锁机制,而jdk1.8采用的是CAS+synchronized
为什么CHM不允许空键呢?
这是因为在map的get方法返回null时存在二义性,并不能确定是没有该key还是存在该key但是值为null,这时就需要containsKey()来判断,但是在多线程情况下,该判断是不安全的,可能在第一个线程进行判断的时候第二个线程就put(key, null)了,而在非并发安全的map中则不需要考虑这点。
至于value为什么不能为空,没找到合理的设计依据,可能只是设计者的个人偏好?