HashMap和Hashtable的区别
HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,在只有一个线程访问的情况下,效率要高于Hashtable。
如何决定使用HashMap还是TreeMap
对于Map中插入、删除和获取元素这类操作,HashMap是最好的选择
然而,假如需要对一个有序的key集合进行遍历,TreeMap是更好的选择。
HashMap和ConcurrentHashMap的区别
HashMap不是线程安全的,而ConcurrentHashMap是线程安全的
ConcurrentHashMap采用锁分段技术,将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段segment,然后再这个片分段上面进行插入,而且这里还需要获取segment锁。
ConcurrentHashMap让锁的粒度更精细一些,并发性能更好。
谈一下HashMap中put是如何实现的
计算关于key的hashcode值(于Key.hashcode的高16位做异或运算)
如果散列表为空时,调用resize()初始化散列表
如果没有发生碰撞,直接添加元素到散列表中去
如果发生了碰撞(hashcode值相同),进行三种判断
若key地址相同或者equals后内容相同,则替换旧值
如果是红黑树结构,就调用树的插入方法
链表结构,循环遍历直到链表中某个节点为空,尾插法进行插入,插入后判断链表个数是否达到变成红黑树的阙值8;也可以遍历到有节点与插入元素的哈希值和内容相同,进行覆盖。
如果桶满了大于阀值,则resize进行扩容
谈一下HashMap中什么时候需要进行扩容,扩容resize()又是如何实现的?
调用场景:
初始化数组table
当数组table的size达到阙值时即++size>load factor*capacity时,也是在putVal函数中
实现过程:
通过判断旧数组的容量是否大于0来判断数组是否初始化过
否:进行初始化
判断是否调用无参构造器
是:使用默认的大小和阙值
否:使用构造函数中初始化的容量,当然这个容量是经过tableSizefor计算后的2次幂数
概括的讲:扩容需要重新分配一个新数组,新数组是老数组的2倍长,然后遍历整个老结构,把所有的元素挨个重新分配到新结构中去。
PS:可见底层数据结构用到了数组 ,到最后会因为容量问题都需要进行扩容操作
HashMap中get是如何实现的?
对key的hashcode进行hashing,与运算计算下标获取bucket位置,如果在桶的首位上就可以找到就直接返回,否则在树中找或者链表中遍历找,如果hash冲突,则利用equals方法遍历链表查找节点。
为什么不直接将key作为哈希值而是与高16位做异或运算?
因为数组位置的确定用的是与运算,仅仅最后四位有效,设计者将key的哈希值与高16位做异或运算使得在做&运算确定数组的插入位置时,此时的低位实际时高位与低位的结合,增加了随机性,减少了哈希碰撞的次数。
为什么是16?为什么必须是2的幂?如果输入值不是2的幂比如10会怎么样?
HashMap默认初始化长度为16,并且每次自动扩展或者是手动初始化容量时,必须是2的幂。
为了数据的均匀分布,减少哈希碰撞。因为确定数组位置是用的位运算,若数据不是2的次幂则会增加哈希碰撞的次数和浪费数组空间。(PS:其实若不考虑效率,求余也可以就不用位运算了也不用长度必须为2的次幂)
输入数据若不是2的次幂,HashMap通过一通位移运算和或运算得到的肯定是2的幂次数,并且是离那个数最近的数字
当两个对象的hashcode相等时会怎么样?
会产生哈希碰撞,若key值相同则替换旧值,不然链接到链表后面,链表长度超过阙值8就转为红黑树存储
如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
超过阙值就会进行扩容操作,概括的讲就是扩容后的数组大小是原数组的2倍,将原来的元素重新hashing放入新的散列表中去。
PS:hashing(散列法或哈希法)的概念 散列法(Hashing)是一种将字符组成的字符串转换为固定长度(一般是更短长度)的数值或索引值的方法,称为散列法,也叫哈希法。由于通过更短的哈希
传统HashMap的缺点(为什么引入红黑树?):
JDK1.8以前HashMap的实现是数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当HashMap中有大量的元素存放在同一个桶中时,这个桶下有一条长长的链表,这个时候HashMap就相当于一个单链表,加入单链表有n个元素,遍历的时间复杂度就是O(n),完全失去了它的优势。针对这种情况,JDK1.8中引入了红黑树(查找时间复杂度为O(logn))来优化这个问题。
平时在、使用HashMap时一般使用什么类型的元素作为Key?
选择Integer,String这种不可变的类型,像String的一切操作都是新建一个String对象,对新的对象进行拼接分割等,这些类已经很规范的覆写了hashCode()以及equals()方法。作为不可变类天生是线程安全的。