HashMap的底层数据结构是什么
JDK1.7
JDK1.7中,是由数组+链表组成的,数组是HashMap的主体,而链表是为了解决冲突而存在的。
插入链表时采用的是头插法,在并发情况下,会出现成环现象。
JDK1.8
JDK1.8中,是由数组+链表+红黑树存在的
当链表过长时,会影响HashMap的查询性能,红黑树的查找时间复杂度为O(log2N),优于链表的O(n)。
当链表长度 > 8 && 数组长度 >=64的时候会转化为红黑树。
如果数组长度小于64,那么就会先扩容。
当红黑树的个数小于6的时候,红黑树会自动转化成链表
为什么使用红黑树,而不是其他的树结构呢
二叉树
红黑树是不严格平衡二叉树,插入、删除、查找的最坏时间复杂度都是O(log2N),而二叉树会退化成链表时间复杂度为O(n)
二叉平衡树
二叉树是高度平衡的二叉树,查找效率高,但是为了维持这种高度的平衡,每次插入和删除都要做调整,比较复杂耗时。
二叉树的查找优于红黑树,但是插入和删除比不上红黑树
另外,红黑树的性能指标比较稳定,插入、删除、查找都是O(log2N)
B和B+
B+树在数据量不是很多的情况下,数据会全部挤满在一个节点上面,这时候的查找效率就会退化成链表
B树和B+树都很矮胖,比较适合用于磁盘的场景,而红黑树比较适合内存中排序。
红黑树怎么保持平衡的
使用旋转(左旋和右旋)和染色
解决hash冲突的办法有哪些?HashMap用的那种
hashmap使用的是拉链法
- 开放地址法
- 再散列法
- 拉链法
- 建立公共溢出区
为什么在解决hash冲突的时候,不直接选择红黑树呢
因为红黑树需要左旋、右旋和染色来保持平衡,而单链表不需要这些操作。
-
一开始使用链表,单链表可以满足查询的性能,而红黑树新增节点比较慢。
-
当大于8的时候,需要红黑树满足查询性能
HashMap和HashTable的区别
- 线程安全:HashTable方法的sychonized修饰,线程安全,但是效率比hashmap低
- 底层数据结构:hashmap当链表长度大于8的时候,会转化成红黑树,但是hashtable一直是链表
- 初始容量和扩容机制:hashtable默认为11,扩容为2n+1;hashmap默认为16,库容为2的幂次方大小
- hashmap支持一个null的key和多个null的value,但是hashtable的key和value都不支持null,否则会报空指针异常
JDK1.7和1.8的区别
- 数据结构
- 数组+单链表
- 数组+链表+红黑树
- 插入方式
- 头插法,多线程可能成环
- 尾插法
- 扩容
- 7需要全部进行定位
- 8的话位置不变或者当前位置+旧的size大小
- 散列函数
- 7做了四次移位或者四次异或
- 8只做了一次
扩容因子为什么是0.75呢
扩容因子的话标识的是HashMap的疏密程度。
- 当扩容因子过大时,虽然空间利用率高,但是哈希冲突增加不利于查找
- 当扩容因子过小时,虽然哈希冲突减少,但是空间利用率不高
- 当扩容因子在0.75的时候,遵循参数为0.5的泊松分布。另外在这种情况下,当随机添加值的时候,一个链表的长度大于8,几乎是一个不可能的事件。
hashmap的扩容流程
扩容的条件
当HashMap中的元素超过(数组长度)* 负载因子,或者链表过程时就会进行数组扩容。
新节点的位置
- HashMap扩容后,节点要不在原来的位置,要不就分配到“原位置+旧容量”的位置。
- 将所有的元素分为了低位链表(存放下标没变的)、高位链表(存放已经变的)
- 然后将高低两个链表插入到新数组中
HashMap的key存储索引是怎么计算的
- 根据Key值计算出hashcode值
- 根据hashcode计算出hash的值
- 然后通过hash & (length - 1)计算出存储的位置
HashMap的长度为什么是2的幂次方
为什么把key的哈希码右移16位呢
初始化HashMap的时候,如果传入17,它会怎么处理呢?
- 如果初始化HashMap的时候,如果传入的不是2的n次幂,那么HashMap会向上寻找距离最近的2的n次幂的值,如果是17,那么就初始化位32
为什么HashMap链表转红黑树的阈值为8呢
- 理想情况下,随机的哈希码,链表里的节点符合泊松分布,节点个数为8的概率几乎为0。而红黑树是保证极端情况下的查找效率。
红黑树回转成链表阈值为什么是6,而不是8
- 如果回转的阈值也是8,那么可能会导致链表和红黑树不断转换,导致资源的浪费
put方法的流程
HashMap怎么查找元素
HashMap为什么线程不安全
- 多线程扩容死循环
- 1.7由于采用头插法,在多线程环境下,可能导致环形链表的问题
- 1.8没有这种问题
- 多线程put可能导致元素丢失
- 多线程执行put操作时,如果索引位置相同,那么前面的key可能被后一个key覆盖
- put和get并发时,可能导致get为null
- 如果put的时候,元素超出个数突然rehash,此时执行get,就会导致null值