HashMap
Java HashMap 底层采用哈希表结构(数组+链表、JDK1.8后为数组+链表或红黑树)实现,结合了数组和链表的有点:
- 数组有点:通过组下标可以快速实现对数组元素的访问,效率极高。
- 链表有点:插入或删除数据不需要移动元素,只需修改节点引用,效率极高。
HashMap 通过hash 方法计算key的哈希码,然后通过(n-1)&hash公式(n为数组长度)得到key在数组中存放的下标。当两个key在数组中存放的下标一致时,数据将以链表的方式存储(哈希冲突,哈希碰撞)。在链表中查找数据必须从第一个元素开始一层一层往下找,直到找到为止,时间复杂度为O(N),所以当链表长度越来越长时,HashMap的效率越来越低。
JDK1.8采用数组+链表+红黑树结构来实现HashMap。当链表中的元素超过8个并且数组长度大于64,会将链表转换为红黑树,转换后数据查询时间复杂度为O(logN)。
put操作过程总结:
- 判断HashMap数组是否为空,是的话初始化数组。(在创建HashMap对象的时候并不会直接初始化数组);
- 通过(n-1)&hash计算key在数组中的存放索引;
- 目标索引位置为空的话,直接创建Node存储(红黑树节点使用TreeNode);
- 目标索引位置不为你空的话,分下面三种情况
key相同,覆盖旧值
该节点类型是红黑树的话,执行红黑树插入操作
该节点类型是链表的话,遍历到最后一个元素尾插入,如果期间遇到有相同key,则直接覆盖。如果链表长度大于等于8并且数组容量大于等于64,则将链表转换为红黑树结构。 - 判断HashMap元素个数是否大于等于threshold,是的话,进行扩容操作。
HashTable
HashTable是Map接口线程安全实现版本,数据结构和方法实现与HashMap类似。
put添加指定键值对,键和值都不能为null
HashTable和HashMap对比
- 线程是否安全:HashMap是线程不安全的,HashTable是线程安全的。
- 对Null key 和Null value的支持:HashMap中,null可以作为健,这样的健只有一个,可以有一个或多个键所对应的值为null;HashTable中key和value都不能为null,否则抛出空指针异常;
- 初始容量大小和每次扩容量大小的不同:创建时如果不指定容量初始值,HashTable默认的初始大小为11,之后每次扩容,容量变为原来的2n+1。HashMap默认的初始化大小为16,之后每次扩容,容量变为原来的2倍;
创建时如果给定了容量初始值,那么HashTable会直接使用你给给定的大小,HashMap会将其 扩充为2的幂次方大小。 - 底层数据结构:JDK1.8及以后的HashMap在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认8)时,将链表转化为红黑树,以减少搜索时间,HashTable没有这样的机制。
HashSet
HashMap的key是不允许重复的,这也正好符合Set的特性。因为HashSet内部采用HashMap存储数据,所以HahSet可以存储null值,支持快速失败,非线程安全。