Java集合框架学习笔记(三):HashMap和HashSet

207 阅读5分钟
原文链接: www.geekmuseo.com

一、HashMap

1、几个重要变量

size : 当前元素个数

modCount:这个变量在每次执行更新的时候都会加一

loadFactor:加载因子,用于计算阈值,一般在0~1之间threshold:这个变量表示容量阈值,超过这个阈值就需要扩容,threshold = capacity * loadFactor

DEFAULT_INITIAL_CAPACITY :初始化大小,为16

DEFAULT_LOAD_FACTOR:默认的加载因子,为0.75

MAXIMUM_CAPACITY:最大允许的容量,为107374182

2、底层数据结构

2.1、Node节点

如图所示

每个节点包含key的hash值、key、value以及指向下一个节点的指针。key的hash值和key在初始化node后就不能变,value可以改变。不同的key由不同的hash值,但是映射到容量有限的数组中时,可能映射到同一个位置,这时候就发送了hash冲突,这就像取模运算,3 mod 2 和 5 mod 2的结果是一样的,但是3不等于5。对于冲突的数据,通过头部插入的方式形成一张链表,从而解决冲突。

2.2、Node数组

HashMap底层使用一个Node数组保存数据,对于数组同一个位置的不同key,则通过链表的形式保存,因此HashMap底层数据结构就是数组加链表:

2.3、扩容

HashMap默认初始大小为16,加载因子为0.75,因此阈值为12,当向HashMap插入第13个元素时,会发生扩容。HashMap每次扩容都是原来容量的两倍,阈值也会重新调整。HashMap会申请一个新的数组,然后把数据全部重新散列到新的数组,由于每次扩容2倍,因此原来位置为2的倍数的节点,重新散列后节点位置不会变。其他元素(包括hash冲突导致的链表上的元素)则会重新散列。

3、put操作

put操作顺序如下:

1、计算key的hash值

2、判断当前容量是否达到阈值,如果达到则进行扩容

3、通过key的hash值计算数组位置,如果不冲突,则直接new一个Node节点,然后放在数组位置上

4、如果冲突,判断已存在的Node节点的key值以及hash值和put的是否一样,如果一样,就用新值替换旧值,如果不一样,则new一个Node节点插入到头部,并将Node节点的next指向已存在的节点。

5、size加一,然后再次判断是否要扩容

4、get操作

get操作执行顺序如下:

1、计算key的hash值

2、根据key的hash值计算数组位置

3、如果数组位置有Node节点,则遍历这个Node节点的链表,当且仅当key以及hash都一样的时候,则认为找到了,然后返回value值

4、如果没找到则返回null

5、remove操作

1、计算key的hash值

2、根据key的hash值计算数组位置

3、如果数组位置有Node节点,则遍历这个Node节点的链表,当且仅当key以及hash都一样的时候,则认为找到了,然后去掉这个节点

4、size减一

6、containsValue操作

先循环外层Node数组,然后循环遍历Node链表,然后比较key和key的hash值,如果一样则表示存在,直接返回true,否则返回false。

7、JDK8的改进

上面几节的描述都是基于JDK7,在JDK8之前的HashMap版本,由于底层数据结构是数组加链表的结构,当hash冲突很严重的时候,这个HashMap会退化成一个链表,从而get操作以及put操作从O(1)退化成了O(n)。如下图:

JDK8对这部分进行了改造,Node链表如果节点小于8的时候,依然是链表结构,如果节点数量大于8,则会把链表转换成红黑树,由于红黑树的操作为O(logn),因此即使在冲突很严重的情况下,也可以保证较好的性能。如图:

红黑树本质是一颗平衡二叉树,因此它会通过hash值进行划分,每个节点的左子树的hash值一定比该节点的hash值小,每个节点的右子树hash值一定比该节点的hash值大。此外,红黑树会保持下面5条性质:

1、每个结点要么是红的要么是黑的。

2、根结点是黑的。

3、每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。

4、如果一个结点是红的,那么它的两个儿子都是黑的。

5、对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。从而保证它的插入和获取的算法复杂度为O(logn)。

为了实现上面的5个性质,红黑树在插入数据的时候会动态调整整颗数的结构,通过旋转、分裂等操作实现,具体算法可以参考TreeMap或者数据结构教程。

二、HashSetHashSet

底层基于HashMap,它的add方法传入一个值,这个值作为key插入HashMap,对应的value使用一个共享的Object对象。这样当传入的值相同的时候,就不会新生成Node,而是用共享的Object对象进行一次覆盖,本质上没有任何影响。这样就实现了Set中无重复数据的目的。