就业:HashMap实现原理和扩容机制
1.实现原理:
HashMap的底层实现是一个哈希表即数组+链表;
HashMap初始容量大小16,扩容因子为0.75,扩容倍数为2;
HashMap本质是一个一定长度的数组,数组中存放的是链表。
当向HashMap中put(key,value)时,会首先通过hash算法计算出存放到数组中的位置,比如位置索引为i,将其放入到Entry中,如果这个位置上面已经有元素了,那么就将新加入的元素放在链表的头上,最先加入的元素在链表尾。比如,第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起,也就是说数组中存储的是最后插入的元素。
HashMap的get(key)方法是:首先计算key的hashcode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。从这里我们可以想象得到,如果每个位置上的链表只有一个元素,那么hashmap的get效率将是最高的。所以我们需要让这个hash算法尽可能的将元素平均的放在数组中每个位置上_。_
2.扩容机制
当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容。
[Java]纯文本查看__复制代码
1
2
3
4
5
6
static
final
int
DEFAULT_INITIAL_CAPACITY =
1
<<
4
`;
// HashMap初始容量大小(16)`
static
final
int
MAXIMUM_CAPACITY =
1
<<
30
`;
// HashMap最大容量`
transient
int
size;
// The number of key-value mappings contained in this map
static
final
float
DEFAULT_LOAD_FACTOR =
0
`.75f;
// 负载因子`
HashMap的容量size乘以负载因子[默认
`0.75`] = threshold;
// threshold即为开始扩容的临界值
transient
Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
// HashMap的基本构成Entry数组
当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置。
0.75这个值成为负载因子,那么为什么负载因子为0.75呢?这是通过大量实验统计得出来的,如果过小,比如0.5,那么当存放的元素超过一半时就进行扩容,会造成资源的浪费;如果过大,比如1,那么当元素满的时候才进行扩容,会使get,put操作的碰撞几率增加。
HashMap中扩容是调用resize()方法,方法源码:
[Java]纯文本查看__复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void
resize(
`int
newCapacity) {`
Entry[] oldTable = table;
int
oldCapacity = oldTable.length;
//如果当前的数组长度已经达到最大值,则不在进行调整
if
(oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return
`;`
}
//根据传入参数的长度定义新的数组
Entry[] newTable =
new
Entry[newCapacity];
//按照新的规则,将旧数组中的元素转移到新数组中
transfer(newTable);
table = newTable;
//更新临界值
threshold = (
`int`)(newCapacity * loadFactor);
}
//旧数组中元素往新数组中迁移
void
transfer(Entry[] newTable) {
//旧数组
Entry[] src = table;
//新数组长度
int
newCapacity = newTable.length;
//遍历旧数组
for
(
`int
j =
0`; j < src.length; j++) {
Entry<K,V> e = src[j];
if
(e !=
null
`) {`
src[j] =
null
`;`
do
{
Entry<K,V> next = e.next;
int
i = indexFor(e.hash, newCapacity);
`//放在新数组中的index位置`
e.next = newTable;
`//实现链表结构,新加入的放在链头,之前的的数据放在链尾`
newTable = e;
e = next;
}
while
(e !=
null
`);`
}
}
}
可以看到HashMap不是无限扩容的,当达到了实现预定的MAXIMUM_CAPACITY,就不再进行扩容。