个人经过研究之后随便写点自己可以理解的内容~纯粹属于学习笔记类的整理
hashmap底层是数组加链表的结构,它本身是个集合,所以创建一新的hashmap实际上是初始化一个新的数组的过程,数组每一个元素都可能是一个链表结构。
当我们使用put方法往hashmap中存入元素的时候,我们存入的是key-value的结构,那么put调用hashcode方法计算该key的hash值,也就是该key在数组中的下标位置。
上图是hashmap的一个数据结构,当key的存储位置(hash值)确定之后查找此位置是否存在多个元素,存在的话调用equal方法比较链表中的元素的key值跟已存在的是否相同,如果相同覆盖value,不相同就创建新的位置放入key-value。
存储位置计算公式:
hash(key)%(length) key的hash值对数组总长度取模
equal比较的是对象类型、值均一致返回true,但==比较基本类型只比较值,比较对象时比较内存地址,例如:
String s1=new String("abc");
String s2=new String("abc");
System.out.println(s1==s2); false 两个对象内存地址不同,故为false
System.out.println(s1.equals(s2)); true 两个对象类型一致、值相同,故为true
但是看以下例子:
String a ="123";
String b ="123";
System.out.println(a == b); true string类是不可变的,在内存区域中a、b指向一个对象
System.out.println(a.equals(b)); true a、b的值一致,
再看:
int a =123;
int b =123;
System.out.println(a == b); true 对于基本类型==比较值的大小
为什么要重写equals和hashcode方法:
当hashmap中存储的key-value键值对的key是自定义对象时,比如我们自定义两个对象O1,O2。这两个对象有相同的属性值,当我们用O1作为key放入hashmap中,想用O2拿到(O1O2我们假设一样),这样得到null,因为hashmap的存放方式把O1放入一个索引,这个索引是O1的hash值决定,但是O2和O1的hash值默认计算是不一样的,所以我们无法从hashmap中用O2拿到O1
重写hashcode方法:
我们重写hashcode,希望hashmap利用O1和O2的属性值得hash值去存和取,即我们存入O1,用O2试图再次取出O1,但是发现结果依然为null,原因如下图所示:
我们存入O1,索引号是根据id的hash值计算,试图取出O2,hashmap也是根据O2相同的id取,但我们看到hashmap认为O1O2并不是一个对象,所以存储方式变为了链表,我们仍然无法用O2取出O1
所以hashcode重写了也需要重写equals:
我们重写equals,定义了两个对象相比较的规则
我们必须在equals中这样定义:当两个对象同属于自定义类,且相关属性值完全相同,那么可以认为相同,这样hashmap在两个对象拥有同样的hash值比较时,调用equals方法再次比较会认为两个对象相同,那么我们可以用O2取出O1
附:
Java8对于hashmap做了优化,hashmap数组变成了哈希桶数组,如果哈希桶数组很大,即使较差的Hash算法也会比较分散,如果哈希桶数组数组很小,即使好的Hash算法也会出现较多碰撞,所以就需要在空间成本和时间成本之间权衡,其实就是在根据实际情况确定哈希桶数组的大小,并在此基础上设计好的hash算法减少Hash碰撞,也就是避免链表过长的问题。
这里存在一个问题,即使负载因子和Hash算法设计的再合理,也免不了会出现拉链过长的情况,一旦出现拉链过长,则会严重影响HashMap的性能。于是,在JDK1.8版本中,对数据结构做了进一步的优化,引入了红黑树。而当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。
参考博文: