HashMap能够说是java中最多见也是最重要的key-value存储结构类,不少程序员可能常常用,可是不必定清楚这个类背后的数据结构和相关操做原理,为了复习HashMap相关的知识,今天花了一天的时间整理了下有关该类的相关知识,我的认为基本上涵盖了HashMap相关的知识点,但愿对你们有所帮助。java
博客园的编辑功能很差用,截成图后变形严重,所以在这里放个连接,(点我看大图)为了让你们看得更清楚,把图片的内容也放在这里。程序员
一、基本概念算法
size:key-value键值对的数量数组
capacity:Entry数组的大小,默认16安全
loadfactor:负载因子,默认0.75,基于性能和空间的tradeoff,当太大时,会致使冲突变多,影响查询性能,过小时,会占用较多空间,致使浪费数据结构
threshold:极限容量,数组扩容时的临界值,capacity*loadFactor>threshold时,会自动扩容jvm
Entry:map中数组存储的对象,遍历Map时就是基于Entry进行遍历的函数
Bucket:桶,map中一个Entry位置下对应的一个链表,相似于一个坑,相同hashcode的键值对以链表的形式存在一个bucket里性能
modcount:修改计数,当HashMap中的元素个数发生改变时,该值就会+1,若是该值和expectedModCount不一致,会触发fast-fail机制,抛出ConcurrentModifycationException异常线程
二、数据结构
jdk7以前(含jdk7):数组+链表
jdk8开始:数组+链表|红黑树(当链表中元素超过8个时,会由链表自动转为红黑树)
三、重要特性
一、非线程安全,对应HashTable是线程安全的,由于加了synchronized关键字
二、无序,由于hash函数会打乱顺序,而且resize后不保证在新数组中的位置和原数组中位置一致,可是会在原来位置+2^n位置
三、容许key和value均为null,当key为null时,存在entry[0]所在的链表里
四、重要方法
equals方法: Object类定义的方法,具体介绍参考面hash方法。若是两个对象根据equals()方法相等,则hashcode必定相等,反过来若是hashcode相同,则equals不必定相等。重写equals方法须要知足5个特性
一、自反性:x.equals(x)永远返回true
二、对称性:若是x.equals(y)为true,则y.equals(x)也为true
三、传递性:若是x.equals(y)为true,y.equals(z)为true,则x.equals(z)也为true
四、幂等性:若是对象没被改变,那么无论调用多少次,x.equal(y)的结果永远相同
五、非空性:若是x非空,则x.equals(null) 永远为false
hash方法:Object类定义的方法,用于计算key的hashCode,默认是一个对象在JVM内存地址的哈希值。当须要比较对象的值而不是对象自己时,一般须要重写hash方法和equals方法,由于默认的equals方法比较的是对象在jvm内存地址的值,若是只重写hash 方法,那么equals方法比较的实际上是2个对象的堆地址;反过来,若是只重写equals方法,那么相同对象的hashcode可能不一致,也会致使比较结果不正确
indexFor方法:计算hashCode在entry数组中的位置,用了个很巧妙的算法h = h&(table.length -1 ),后面会解释为何每次resize的时候,都是原来的2倍
put方法:put方法先判断key是否为null,若是为null,则调用putForNullKey方法(jdk8以前),不然按下面思路执行put操做:
一、先根据hashCode方法计算该对象的key的hashCode
二、经过indexFor方法计算key的hashCode在entry数组中的下标位置
三、若是该下标处为null,则把该对象存入该节点
四、若是该下标处不为null,则遍历该链表,根据equals方法寻找是否有对应的key,若是有,则替换旧的值,不然将该对象添加到链表的头部
addEntry方法:添加Entry对象的方法,添加以前会先判断是否须要resize扩容,扩容的条件有2个:键值对数量>=极限容量,而且存放该对象的buckey的值非空(也就是有冲突了),假若有极限容量是12,map中有13个键值对,可是这13个键值对都存在table 数组的13个bucket里,那么也不会扩容的
resize方法:当达到扩容条件时调用的方法
一、当极限容量已经达到最大值2^32-1时,再也不扩容
二、若是未达到,则首先建立一个新的数组(容量为原来数组的2倍)
若是初始化Map容量的时候不是2的n此方,会生效吗?
不会的,由于hash会找一个比该值大的最小2的n这次方的值,好比指定了初始容量为12,则默认的数组大小为16
为何是2倍?2个缘由:
一、位移运算速度快。
二、每次将对象转移到新的数组时(即调用indexFor函数时),因为采用的是hash&(length-1),既能减小冲突,又能保证速度,因此每次扩容都是原来的2倍。
三、遍历map,计算原来的数组中每一个键值对在新数组中的index,再将其存到新数组中相应位置
remove方法:与put操做基本相反,将对象从Map中移除,需注意fast-fail问题,对应的解决办法是:采用迭代器自己的remove方法,而不要采用hashMap的remove方法
五、和其余集合类的区别
HashTable:
一、HashTable线程安全,由于put和remove方法里加了synchronized关键字
二、HashTable中的key和value都不能为null,hashMap能够
三、性能上,因为HashMap非线程安全,所以速度更高
四、HashMap继承自AbstractMap,HashTable继承自Dictionary
五、其余各方面区别不大,包括数据结构,存取方法等
TreeMap:一、HashMap无序,TreeMap基于红黑树实现,是有序的
总的来讲,hashMap是一个设计很是巧妙的类,光看源码有必定的复杂度,尤为是不一样版本的jdk对应的方法可能有较大差别,若是有什么地方讲的不正确,欢迎指出。有须要思惟导图原图的能够给我留言,我发给你。