HashMap
https://blog.csdn.net/fujiakai/article/details/51585767
摘要
HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长;
HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆;
多线程环境下可以采用concurrent并发包下的concurrentHashMap;
默认加载因子为0.75;
构造方法中提到了两个很重要的参数:初始容量和加载因子;
扩容是是新建了一个HashMap的底层数组
,而后调用transfer方法,
将旧HashMap的全部元素添加到新的HashMap中
(要重新计算元素在新的数组中的索引位置)。 很明显,扩容是一个
相当耗时
的操作,因为它需要重新计算这些元素在新的数组中的位置并进行复制处理。因此,我们在用HashMap的时,最好能
提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能
。
1、默认16个元素大小;
2、底层主要数据结构是数组,为了解决哈希冲突,所以会有下一层的链表结构;
数组+链表
3、哈希冲突并不是说key的hashCode重复,而是在那块连续的内存单元中,不同的key经过hash算法散列之后指向了同一个地址,
如果出现哈希冲突,则
将最新出现的置于链表头部,即数组哈希地址位置
;
4、负载因子越大,数组剩余空间越少,哈希碰撞会多,链表会长,链表中的元素查询效率则慢,hashMap消耗空间(数组结构的空间)则小;
负载因子越小,数字剩余空间则多,哈希碰撞则小,链表短,寻址快,空间消耗则大;
5、为hashMap分配一块连续的内存用于存储元素,存储位置(下标)根据hash算法尽可能均匀散列,不过还是会出现哈希碰撞,从而产生链表结构;
HashMap底层结构,代码演示
定义一个类型作为key使用,重写hashCode方法
package
collectionandmap;
public
class
KeyBean {
public
KeyBean() {
}
public
KeyBean(Integer
key
, String
name
){
this
.
key
=
key
;
this
.
name
=
name
;
}
private
Integer
key
;
private
String
name
;
public
Integer getKey() {
return
key
;
}
public
void
setKey(Integer
key
) {
this
.
key
=
key
;
}
public
String getName() {
return
name
;
}
public
void
setName(String
name
) {
this
.
name
=
name
;
}
@Override
public
int
hashCode() {//重写hashCode方法
return
this
.
key
.hashCode();
}
@Override
public
boolean
equals(Object
obj
) {
if
(!(
obj
instanceof
KeyBean)){
return
false
;
}
return
((KeyBean)
obj
).getKey().equals(
this
.
key
);
}
}
测试如下
public
static
void
main(String[]
args
) {
Map
map
=
new
HashMap<>();
KeyBean
k1
=
new
KeyBean(1,
"foo"
);
String
s1
=
"foo"
;
map
.put(
k1
,
s1
);
KeyBean
k2
=
new
KeyBean(1,
"Boo"
);
String
s2
=
"Boo"
;
map
.put(
k2
,
s2
);
System.out.println(k1.hashCode());//1
System.out.println(k2.hashCode());//1
KeyBean
k3
=
new
KeyBean(3,
"Zoo"
);
String
s3
=
"Zoo"
;
map
.put(
k3
,
s3
);
System.out.println(k3.hashCode());//3
System.out.println(map.size());//2
System.out.println(map.get(k1));//Boo
System.out.println(map.get(k2));//Boo
System.out.println(map.get(k3));//Zoo
}
详细介绍见:
https://www.cnblogs.com/chengxiao/p/6059914.html
通过以上代码能够得知,当发生哈希冲突并且size大于阈值的时候,需要进行数组扩容,扩容时,需要新建一个长度为之前数组2倍的新的数组,然后将当前的Entry数组中的元素全部传输过去,扩容后的新数组长度为之前的2倍,所以扩容相对来说是个耗资源的操作。
当负载因子较大时,去给table数组扩容的可能性(
哈希地址未被占用
)就会少,所以相对占用内存较少(空间上较少),但是每条entry链上的元素会相对较多,查询的时间也会增长(时间上较多)。反之就是,负载因子较少的时候,给table数组扩容的可能性就高,那么内存空间占用就多,但是entry链上的元素就会相对较少,查出的时间也会减少。所以才有了负载因子是时间和空间上的一种折中的说法。所以设置负载因子的时候要考虑自己追求的是时间还是空间上的少。
所以负载因子越大则散列表的装填程度越高,也就是能容纳更多的元素,元素多了,链表大了,所以此时索引效率就会降低。
反之,负载因子越小则链表中的数据量就越稀疏,此时会对空间造成烂费,但是此时索引效率高
。
对于哈希冲突
事实上,这个优化在JDK 1.8中已经去掉了,因为JDK 1.8中,映射到同一个哈希桶(数组位置)的Entry对象,使用了红黑树来存储,从而大大加速了其查找效率;
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结构,
但是在
jdk1.8里
加入了红黑树的实现,当链表的长度大于8时,转换为红黑树的结构;
1.8在hash算法上的位操作(与运算)运算次数上比之前少两次,由原来的3次变为1次;