阅读 118

HashMap原理与源码解析

HashMap原理

数组是一块连续的内存空间,所以有可能造成内存空间浪费

链表是不连续的空间,但是只能通过顺序访问

01.png 总结:

  • 数组:查改快
  • 链表:增删快

HashMap的数据结构:

02.png

数组+链表

JDK1.8 后用红黑树

HashMap说到底也是一个Map的结构,Map是一组键值对

03.png

如何去存储这样一个键值对呢?

最简单的思路就是用一个数组去存

04.png

但是数组的索引是下标(即数字),不能直接通过key获取到value值

05.png

如上图,还是得通过一个个遍历的方式才能获取到key所对应的value

所以就需要有hash()操作:将key转化为数字(即下标)

06.png

但是还会存在一个哈希碰撞的问题:

所以就会出现链表的结构,将冲突的数据直接添加在后面

数组+链表的结构在代码中如何实现呢?

07.png

只需要在原先的结构上再添加一个next的结构

还有一个问题:这样存储键值对的数组大小应该开多少呢?

总不能只存储一个键值对也采取整个int范围吧?

08.png

09.png 上图采用了999的数组大小(这里只是举个例子)

如果hash() 值大于999时,则需要对其进行mod操作来防止数组越界

所以当两个产生哈希碰撞的对象进行比较时,应该首先比较它们的hash值,因为这样比比较两个对象高效的多

所以也需要存储对象的Hash值:

10.png

HashMap 1.7 源码解析

1.7 HashMap 底层数据结构:

17_struct.png

构造方法

构造方法:无参构造函数会调用这个双参数构造函数

arg1:默认的数组大小 (默认是16)

arg2:默认的满载率 (默认是0.75f)

11.png

双参数构造函数:

12.png

先做边界判断,再赋值满载率和数组大小

调用init()方法:(空方法,是给子类如LinkedHashMap进行实现的)

这个数组所对应的成员变量:Entry<K, V>[] table

13.png

Entry结构:对应着刚才将的key,value, next 和 hash值

14.png

put方法

put()方法:

15.png

先判断是否为EMPTY_TABLE, 使用inflateTable() 将这个table创建出来

inflateTable():

16.png

将大小设置为传入参数的接近2的幂次的大小 ( roundUpToPowerOf2() 方法的作用 )

在这里进行数组创建

( 这里可以忽略initHashSeedAsNeeded() 这个方法)

putForNullKey(): Key为Null的情况

17.png

modCount 对HashMap进行增加和操作时会进行++,如果有多个线程进行访问,则会抛出异常

HashMap不是线程安全的

继续看put() 方法:

hash()方法获取key的hash值:

18.png

indexFor(hash, table.length) 达到取模的效果:

19.png

获取到索引之后:遍历该索引上的列表判断是否为相同的key (先判断hash值,再判断key对象)

如果碰到相同的key就进行更新操作

20.png

如果不存在相同的key,则modCount++

调用addEntry()方法:

21.png

先判断是否需要扩容

createEntry() : 22.png

插入过程:如果索引位置上有节点,那么原本的结点会被新的节点挤到后面(头插法)

23.png

扩容操作:

24.png

transfer(): 将旧HashMap上的节点转移到新的HashMap上:注意此时hash的值是与新的长度相&的结果,所以位置有可能会改变

25.png

put() 过程总结:

26.png

习题

27.png

HashMap 1.8 源码解析

1.8 HashMap 底层数据结构

18_struct.png

构造方法:

28.png

29.png

put() 方法

30.png

hash() 方法:

31.png

putValue()方法:

32.png

文章分类
Android
文章标签