HashMap
-
HashMap的底层数据结构?
是数组 + 链表 + 红黑树的数据结构,每个数组中都保存着key,value的实例 在java7之前Entry,java8叫Node
-
HashMap的存取原理?
存储: 先判断数组是否进行了初始化长度,如果没有就调用初始化方法,默认长度为16 如果有赋值的长度,那么回去大于等于这个数的2的幂次方的数作为数组长度 计算出key的hashcode值,然后取余,把得到的值作为数组的下标(索引), 1.7: 把数据插入到链表的头部 1.8: 把这个新的节点作为头结点 如果两个key相同 根据hashcode找到对应的bucket之后,遍历链表通过equals()方法来对比key值。 如果有,则用新的value取代旧的value 判断数组是否达到了树形化阈值,达到了就转换成红黑树 取出: 先计算出key的hashcode值,找到对应的bucket,可能会有多个entry对象,遍历链表, 通过equals()来比较key值,最后取出entey对象
-
Java7和Java8的区别?
java7之前是头部插入 新来的值会取代原有的值,原有的值就顺推到链表中去,因为作者认为后来的值被查找的可能性更大一点,提升查找的效率。 java8之后是尾部插入 使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了
-
为啥会线程不安全?
因为通过源码看到put/get方法都没有加同步锁,多线程情况最容易出现的就是: 无法保证上一秒put的值,下一秒get的时候还是原值,所以线程安全还是无法保证 多线程进行扩容的时候可能会出现循环链表
-
有什么线程安全的类代替么?
ConcurrentHashMap HashTable 在方法之前都加上了synchronized(锁)
-
HashMap与Hashtable的比较
- HashMap可以保存键值为null,HashTable不允许键值为null:
- HashTable在保存键值的时候会报空指针异常
- 快速失败机制(fail-safe),这种机制会使你此次读到的数据不一定是最新的数据
- 而HashMap做了特殊的处理
- HashTable在保存键值的时候会报空指针异常
- 初始化容量不同:
- HashMap 的初始化容量为: 16
- HashTable的初始化容量为: 11
- 两者的负载因子都是: 0.75
- 扩容机制不同:
- HashMap 扩容为原数组的两倍
- HashTable 扩容为原数组的两倍 + 1
- HashMap可以保存键值为null,HashTable不允许键值为null:
-
默认初始化大小是多少?为啥是这么多?为啥大小都是2的幂?
默认初始化大小为16 = 1 << 4 为了方便位移运算的算法,也就是将Key映射到index的算法(HashCode % 数组长度),但是位移运算的效率要高很多 内部会调用一个方法,把自定义的数组长度更改为 大于等于数组长度的2的幂
-
HashMap的扩容方式?负载因子是多少?为什么是这么多?
调用 resize()方法进行扩容 分为两步: 扩容:创建一个新的Entry空数组,长度是原数组长度 * 0.75。 ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。 Capacity:HashMap当前长度。 LoadFactor:负载因子,默认值0.75f。 负载因子是0.75的时候,空间利用率比较高,而且避免了相当多的Hash冲突,使得底层的链表或者是红黑树的高度比较低, 提升了空间效率。
-
HashMap的主要参数都有哪些?
// 默认初始化长度 DEFAULT_INITIAL_CAPACITY = 1 << 4 // 最大容量 MAXIMUM_CAPACITY = 1 << 30 // 默认负载因子 DEFAULT_LOAD_FACTOR = 0.75f // 树形化阈值 TREEIFY_THRESHOLD = 8 // 解树形化阈值 UNTREEIFY_THRESHOLD = 6 // 树形化的最小容量 MIN_TREEIFY_CAPACITY = 64
-
HashMap是怎么处理hash碰撞的?
hash碰撞: 不同key值,hash值却相同 拉链法: 通过计算key值的hash值,找到对应的bucket,把数据添加到对应的位置(1.7头部/1.8尾部)
-
hash的计算规则?
取key值的hashcode值,然后通过位移算法取余数组长度,这样可以使元素分布尽量均匀些,减少hash碰撞