点赞在看,养成习惯。java
点赞收藏,人生辉煌。web
点击关注【微信搜索公众号:编程背锅侠】,防止迷路。编程
HashMap
基于哈希表的Map
接⼝口实现,是以key-value存储形式存在,即主要⽤用来存放键值对。它的key、value均可觉得null。HashMap
的实现不是同步的,这意味着它不是线程安全的。此外,HashMap
中的映射不是有序的,位置由hashcode
通过运算决定。数组
JDK1.8 以前 HashMap 由
数组+链表
组成的,数组是HashMap
的主体,链表则是主要为了了解决哈希冲突**(两个对象调用的hashCode⽅法计算的哈希码值一致而致使计算的数组索引值相同)而存在的(“拉链法”解决冲突).JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(或者红黑树的边界值,默认为 8)而且当前数组的长度大于64**时【同时知足这两个条件】,此时此索引位置上的全部数据改成使用红⿊树存储。缓存
补充:将链表转换成红黑树前会判断,即便阈值大于8,可是数组长度小于64,此时并不会将链表变为 红黑树。⽽是选择进行数组扩容。安全
这样作的目的是由于数组比较小的时候要尽量避开红黑树结构,这种状况下变为红黑树结构,反而会下降效率,由于红⿊树须要进行左旋,右旋,变色这些操做来保持平衡 。同时数组⻓度⼩于64时,搜索时间相对要快些。因此综上所述为了提升性能和减小搜索时间,底层在阈值大于8而且数组长度大于64时, 链表才转换为红黑树。具体能够参考 treeifyBin 方法。微信
固然虽然增了了红黑树做为底层数据结构,结构变得复杂了,可是阈值大于8而且数组长度大于64时,链表转换为红黑树时,效率也变的更⾼效。数据结构
存取是无序的。 键和值都均可以是null,可是这些键中仅只能有一个是null。 键位置是惟一的,底层的数据结构控制键的位置。 jdk1.8以前数据结构是:链表 + 数组, jdk1.8以后是 : 链表 + 数组 + 红黑树。 阈值(边界值) > 8 而且数组长度大于64,才将链表转换为红黑树,变为红黑树的⽬的是为了高效的查询。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { // 省略 } 复制代码
补充:经过上述继承关系咱们发现一个很奇怪的现象, 就是HashMap已经继承了AbstractMap而 AbstractMap类实现了Map接⼝口,那为什什么HashMap还要在实现Map接⼝口呢?一样在ArrayList中 LinkedList中都是这种结构。框架
据 Java集合框架的创始⼈人Josh Bloch描述,这样的写法是⼀个失误。在java集合框架中,相似这样的写法不少,最开始写Java集合框架的时候,他认为这样写,在某些地方多是有价值的,直到他意识到搞错了。显然的,JDK的维护者,后来不认为这个⼩小的失误值得去修改,因此就这样存在下来了。编辑器
private static final long serialVersionUID = 362498820763181265L;
复制代码
// 默认的初始容量是16 -- 1<<4至关于1*2的4次方---1*16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 复制代码
1 << 4解析,就是讲1对应的二进制左移4位。0000 0001【1】左移4位为0001 0000【16】。
// 集合最大容量的上限是:2的30次幂
static final int MAXIMUM_CAPACITY = 1 << 30; 复制代码
static final float DEFAULT_LOAD_FACTOR = 0.75f;
复制代码
static final int TREEIFY_THRESHOLD = 8;
复制代码
// 当桶(bucket)上的结点数小于这个值时树转链表
static final int UNTREEIFY_THRESHOLD = 6; 复制代码
// 桶中结构转化为红黑树对应的数组长度最小的值
static final int MIN_TREEIFY_CAPACITY = 64; 复制代码
当Map⾥面的数量超过这个值时,表中的桶才能进行树形化 ,不然桶内元素太多时会扩容,⽽不是树形化。目的是为了不进⾏扩容、树形化选择的冲突,这个值不能⼩于 4 * TREEIFY_THRESHOLD (8)
// 存储元素的数组
transient Node<K,V>[] table; 复制代码
table在JDK1.8中咱们了解到HashMap是由
数组加链表加红⿊树
来组成的结构其中table就是HashMap 中的数组,jdk8以前数组类型是Entry<K,V>类型。从jdk1.8以后是Node<K,V>类型。只是换了个名字, 都实现了同样的接口:Map.Entry<K,V>。负责存储键值对数据的。
// 存放具体元素的集合
transient Set<Map.Entry<K,V>> entrySet; 复制代码
java语言的关键字,变量修饰符,若是用transient声明一个实例变量,当对象存储时,它的值不须要维持。换句话说,用transient关键字标记的成员变量不参与序列化过程。
当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。
// 存放元素的个数,注意这个不等于数组的长度。
transient int size; 复制代码
size为HashMap中K-V的实时数量,不是数组table的⻓度。
// 每次扩容和更改map结构的计数器
transient int modCount; 复制代码
// 临界值 当实际⼤小(容量*负载因⼦)超过临界值时,会进行扩容
int threshold; 复制代码
// 加载因子
final float loadFactor; 复制代码
loadFactor加载因子,是⽤来衡量 HashMap 中元素满的程度,表示HashMap的疏密程度,能够影响hash操做到同一个数组位置的几率,计算HashMap的实时加载因子的方法为:size/capacity,⽽不是占用桶的数量去除以capacity。capacity 是桶的数量,也就是 table 的⻓度length。size是集合中实际存储元素的个数。
loadFactor太⼤致使查找元素效率低,过小致使数组的利用率低,存放的数据会很分散。loadFactor 的默认值为0.75f是官方给出的⼀个比较好的临界值。
当HashMap⾥面容纳的元素已经达到HashMap数组⻓度的75%时,表示HashMap太挤了,须要扩容,⽽而扩容这个过程涉及到 rehash、复制数据等操做,很是消耗性能。因此开发中尽量减小扩容的次数,能够经过建立HashMap集合对象时指定初始容量来尽量避免。
同时在HashMap的构造器中能够定制loadFactor。这个不经常使用。这个构造方法会在接下来的文章中讲解。
创做不易, 很是欢迎你们的点赞、评论和关注(^_−)☆ 你的点赞、评论以及关注是对我最大的支持和鼓励,而你的支持和鼓励 我继续创做高质量博客的动力 !!!