HashMap和Hashtable的区别
好的面试官,对于这个问题我想从两方面来回答。
首先,这两个集合的相同点就在于它们都实现了Map接口。
其次,不同点就在于HashMap是线程不安全的,Hashtable是线程安全的。
而且HashMap允许空键值,由于非线程安全,在单线程访问的情况下,
效率要高于hashtable。
如何决定使用HashMap还是TreeMap
如果要在Map中插入,删除和获取元素这类操作,最好选用HashMap
但是,假如要对一个有序的key集合进行遍历,TreeMap是最好的选择。
HashMap和ConcurrentHashMap的区别
好的面试官。首先从线程安全性上而言,HashMap是非线程安全的,ConcurrentHashMap是线程安全的。
其次,ConcurrentHashMap采用了锁分段技术, 将整个hash桶进行了分段,
也就是将这个大的数组分成了几个小的片段, 而且每个小的片段上都有锁存在,
那么在插入元素的时候, 会先找到对应的片段, 然后在这个片段上进行插入,
而且这里还需要获取setment锁。
最后,ConcurrentHashMap让锁的精度更精细一些, 并发性能更好。
谈一下HashMap的底层原理
首先基于hashing原理, jdk8之后采用数组+链表+红黑树的数据结构。
HashMap中通过put和get来插入和获取对象。
当我们给put传递键和值的时候,put会先对key进行一个hash运算
得到在bucket数组中的位置来存储Entry对象。
在获取对象时,通过get获取到bucket的位置,再通过键对象的equals方法
来找到正确的键值对,然后返回值对象。
谈一下HashMap中put是如何实现的
首先, put会对key进行hash运算,的到HashCode值,
判断hashCode值是否相同,也就是看有没有发生hash碰撞
如果没有发生hash碰撞, 直接将元素添加到散列表中
如果散列表为空, 则调用resize对散列表进行初始化
如果发生了碰撞, 则会进行三种判断
第一种, 如果key地址相同或者进行equals比较后的内容相同, 则替换掉旧的值
第二种, 如果是红黑树结构, 则直接调用树的插入方法
第三种, 就是链表, 循环遍历链表, 直到链表中的某个节点为空, 进行尾插法插入,
插入后再判断链表的个数是否达到变成红黑树的阈值8; 也可以遍历到有节点与插入的元素
的hashCode值和内容相同, 进行覆盖。
最后,如果桶满了大于了阈值, 则会resize进行扩容.
HashMap啥时候扩容,为什么扩容?
当HashMap元素中个数达到扩容阈值, 默认是12的情況下, 会自动触发扩容。
默认扩容大小是原来数组的2倍, HashMap的最大容量是Integer.Max_VALUE,
也就是2的31次方-1。
谈一下HashMap中get是如何实现的?
对key的hashCode进行hashing, 计算下标在bucket中的位置, 如果在bucket(桶)的首位
就可以找到就直接返回, 否则就在树中找或在链表中遍历找, 如果遇到了hash冲突,利用
equlas方法去遍历链表查找节点。
为什么不直接将key作为哈希值而是与高16位做异或运算
因为在数组中位置的确定用的是与运算, 只有后四位有效, 设计者将key的hashCode值
与高16位做异或运算使得在做与运算确定数组的插入位置时, 此时低位实际是高位与低位
的结合, 增加了随机性, 减少hash碰撞的次数.
HashMap的的扩容有什么作用?
扩容的目的是为了减少hash碰撞
原因就在于如果hash表中的元素过多, 可能出现碰撞的概率就越高,
hash碰撞的概率越高, 也就意味着整体的查询效率就越差。
为什么是16, 为什么必须是2的次幂? 如果输入的值不是2的幂像10这样的数字会怎样?
首先, HashMap中默认的初始长度为16, 我们给HashMap手动初始化
容量大小的数字也必须是2的次幂。
至于为什么是16, 为什么必须是2的次幂。主要是为了让HashMap中
的元素均分, 减少hash碰撞的次数。如果输入的数字不是2的次幂,
会增加hash碰撞的次数, 并且还会浪费数组的空间。
就算輸入的值不是2的幂像10这样的数字, HashMap会通过一通位移运
算和或运算, 最终的得到还是2的次幂, 而且是离输入的数字最近的数字。
请解释一下HashMap的参数loadFactor, 它的作用是什么?
loadFactor负载因子表示HashMap中的拥挤程度, 影响到操作数组
同一位置的概率, loadFactor的默认大小为0.75, 当HashMap中的
元素个数达到HashMap中数组的长度的75
HashMap中的构造器中可以定制loadFactor。
如果HahsMap的大小超过了负载因子(loadFactor)定义的容量, 怎么办?
超过阈值会进行扩容, 概括的讲就是扩容后的新数组大小为原来旧数组
的两倍, 并且將原来的元素重新hashing放入到新的散列表中。
传统HashMap的缺点(为什么引入红黑树?):
jdk1.8以前HashMap底层实现是数组+链表, 即使它的哈希函数取得
再好, 也很难达到百分百的让元素均匀分布。当HashMap中有大量的
元素都存入到同一个桶中时, 这个桶下有一条长长的链表, 这个时候
HashMap就相当于单链表, 遍历的时间复杂度为O(n), 完全失去了它
的优势。针对这种情况, jdk1.8中引入了红黑树, 查找时间复杂度为
O(longn), 来优化这个问题。
平时在使用HashMap时,一般用什么类型的元素作为key?
一般选择Integer,String这种不可变的类型, 像对String的一切
操作都是新建一个String对象, 对新的对象进行分割等。
这些类已经很规范的覆写了hashCode和equals方法, 作为不可变
类型, 天生就是线程安全的。