在jdk1.7之前HashMap是基于数组和链表实现的,而且采用头插法。
而jdk1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。采用尾插法。当从红黑树删除结点时也需要判断,如果删除后结点数小于6,则将红黑树转为单链表。
为什么是6不是8:如果是8,在删除后又进行添加,就会造成红黑树与单链表之间的重复转化,设置为6起到缓冲的作用。
HashMap默认的初始化大小为 16。当HashMap中的元素个数之和大于负载因子*当前容量的时候就要进行扩充,容量变为原来的 2 倍。(这里注意不是数组中的个数,而且数组中和链/树中的所有元素个数之和!)
HashMap是线程不安全的,其主要体现:
1.在jdk1.7中,在多线程环境下,扩容时会造成环形链或数据丢失。
2.在jdk1.8中,在多线程环境下,会发生数据覆盖的情况。
(按照我的方法举两个例子就明白了)
扩容时,e.hash & oldCap==0的元素留在原来的位置,否则新位置=旧位置+oldcap
e.hash & oldCap0的元素留在原来的位置则说明 e.hash & (oldCap-1) = e.hash & (2oldCap-1),因为oldCap是2的n次方,所以只有最高位为1,其余位是0,e.hash & oldCap0只有当e.hash与oldCap对应的位置上为0才成立。当e.hash的这个位置为0时,e.hash & (oldCap-1)与e.hash & (2oldCap-1)是相等的,因为
(2oldCap-1)相对于(oldCap-1)而言只是多了1位1,该位对应hash值的位置上为0,所以该位与hash值上对应位上 & 出来的结果为0。所以e.hash & (oldCap-1) = e.hash & (2oldCap-1)。
例如:oldCap = 4 = 0100,8 = 1000,hash1 = 5 = 0101,hash2 = 9 =1001
hash1 & oldCap =0101 & 0100 =0100 != 0
hash2 & oldCap =1001 & 0100 =0000 = 0
hash1 2^2 位上为0所以 hash2 & (oldCap-1) = 1001 & 0011= 1001 & 0111 = hash2 & (2oldCap-1) ,所以在新旧数组中位置相同。
通过阅读源码,可以从jdk1.7和1.8两个方面来回答
根据key通过哈希算法与运算得出数组下标
如果数组下标元素为空,则将key和value封装为Entry对象(JDK1.7是Entry对象,JDK1.8是Node对象)并放入该位置。
如果数组下标位置元素不为空,则要分情况
(i) 如果是在JDK1.7,则首先会判断是否需要扩容,如果要扩容就进行扩容,如果不需要扩容就生成Entry对象,并使用头插法添加到当前链表中。
(ii) 如果是在JDK1.8中,则会先判断当前位置上的TreeNode类型,看是红黑树还是链表Node
(a) 如果是红黑树TreeNode,则将key和value封装为一个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value。
(b) 如果此位置上的Node对象是链表节点,则将key和value封装为一个Node并通过尾插法插入到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历过程中会判断是否存在当前key,如果存在则更新其value,当遍历完链表后,将新的Node插入到链表中,插入到链表后,会看当前链表的节点个数,如果大于8,则会将链表转为红黑树。
(c) 将key和value封装为Node插入到链表或红黑树后,再判断是否需要扩容,如果需要扩容,就结束put方法。
HashTable的操作几乎和HashMap一致,主要的区别在于HashTable为了实现多线程安全,在几乎所有的方法上都加上了synchronized锁,而加锁的结果就是HashTable操作的效率十分低下。
区别
1)Hashtable是线程安全,而HashMap则非线程安全
2)HashMap可使用null作为key,Hashtable不允许null作为key
3)HashMap是对Map接口的实现,Hashtable对Map接口的实现和对Dictionary抽象类的继承
4)HashMap的初始容量是16,Hashtable初始容量是11,两者的填充因子默认都是0.75
5)HashMap与Hashtable计算hash的是方法不同
HashMap是直接实现了Map接口,而TreeMap则是实现了NavigableMap接口,而这个NavigableMap接口拓展了SortedMap接口,SortMap接口又拓展了Map接口。
**TreeMap.forEach默认执行中序遍历,按key的升序遍历所有结点。
区别:
1、HashMap无序,TreeMap有序。
2、HashMap覆盖了equals()方法和hashcode()方法,这使得HashMap中两个相等的映射返回相同的哈希值;TreeMap则是实现了SortedMap接口,使其有序。
3、HashMap的工作效率更高,而TreeMap则是基于树的增删查改。更推荐使用HashMap。
4、HashMap基于哈希桶实现,TreeMap是基于红黑树实现。
5、两者都不是线性安全的。
效率区别:
TreeMap底层是由树(红黑树)实现的,而HashMap是由哈希桶实现的。由于哈希算法本身的优势,我们再进行增删查改的时候。HashMap的时间复杂度是O(1),是通过哈希函数计算的哈希地址。而我们的红黑树就不具有这样的优势时间复杂度是O(log2n)。
与HashMap相比,有如下特点
1. 并发安全
2. 支持一些原子操作
3. 支持高并发,读操作完全并行,写操作支持一定的并行
4. 与容器Collections修饰的synchronizedMap想比,迭代不需要加锁,不会抛出与异常
5. 内部使用CAS算法实现,并发性能高。
其高并发的机制主要有以下两点:分段锁、读不需要锁。