hashmap的数据结构:数组+链表+红黑树 链表是为了解决hash冲突 node上有next指针
1.8以前没有红黑树 为什么要加
1.如果用户的hashcode实现的好,那么hashcode就均匀,碰撞的概率就低,变成红黑树的概率就低
2.如果用户实现的hashcode不好,那么碰撞的概率就高,红黑树出现的概率就高,由于不能控制用户实现hashcode的方法,所以防止极端条件下,hashmap查询性能低下,引入红黑树,并且链表长度超过8,就变为红黑树,8这个数字出现的概率为千万分之一,所以选择了8
3.红黑树在大量hash碰撞的场景可以牺牲空间换取查询效率,但是十分消耗内存空间,树形结构所占用空间是一般节点的2倍,所以使用hashmap时,如果出现了大量的红黑树结构,就要考虑优化hashcode算法
4.链表的查询时间复杂度是O(n) 也就是链表越长,查询效率越低,而红黑树是自平衡的二叉树,可以有效的减少树深,性能更好O(logn),因为他的自平衡性,不会像常规二叉树那样出现单一分支越来越长的问题,越长就越接近链表,查询速度下降
2.1.8以前是头插法,容易造成死链,1.8后改成了尾插法
死链:
扩容的时候,旧数组的数据迁移到新的数组,头插法会使新链表上的元素逆序排序,并发的场景,容易形成死链
死链一旦形成,那么map.get方法被调用时,就会造成cpu使用率飙升100%
put选址:
先计算key所在类的hashcode,根据hashcode确定位置,如果位置上没有元素,直接添加entry,如果已经有了元素,那么需要调用key所在类的equals方法,对链表上的所有元素的key进行比较,如果都是false
说明这个key是个新的,直接加到链表后面,如果其中某个返回了true,说明是个旧的,直接覆盖旧的value
内存分配:
懒加载,先不分配内存,直到使用的时候,再初始化一个长度为16的数组
扩容:
加载因子是0.75 也就是长度达到四分之三时,开始扩容,扩容为原来数组长度的2倍,先扩容,然后复制旧数组的数据,最后再新增元素
链表转红黑树:
数组的长度超过64并且链表的长度超过8 链表变红黑树 但不是变了之后就一直是红黑树,当树的节点数量小于6,还会重新变为链表
红黑树:
自平衡二叉树,左侧的元素都比根节点要小,右侧元素都比根节点要大
hashmap的一些特点:
1.允许key=null value=null
2.key=null只能有一个
3.无序
4.线程不安全 concurrenthashmap线程安全用了volatile关键字 和 分段加锁实现的
延伸:
数组和链表的区别
数组是顺序表,随机访问的时候,查询效率好,因为有角标,但是插入麻烦,因为要维护顺序,比如123中23之间插入一个元素,那么新元素变为3,原来的3变成4的同时还要往后移
链表是虽然也有序,但是没有角标,查找只能遍历链表,找到为止,所以时间复杂度是O(n) 但是插入和删除比数组要强
数组加链表也叫散列表,redis也用了这种结构