关于HashMap

先了解下相关概念:

哈希(Hash):一般叫做散列,意思就是把一堆任意长度的字符串、数字或者二进制输入通过一定的算法(非常多的哈希算法)生成固定长度的一个数字(字符串)。因为算法原因,不同的输入就会得到不同的哈希值。

哈希表(Hash Table):一般叫做散列表,就是通过把键值计算出Hash值后,通过Hash值映射到表里面的某个位置。那么同样的键值,下次访问或者修改都是同一个映射位置,不同的键值因为计算出Hash值不一样映射的位置也会不同。

在java代码中看,hashTable就是一个数组。


image.png

hashCode:是一个数字,代表在hash表中的位置。

HashMap(jdk1.7)

数据结构:

是一个数组+链表的结构。

横向是数组,纵向是链表,之所以这样设计是为了综合利用数组的查找快速的优势 和 链表插入方便的优势。

HashMap的插入过程:

1)先把传入的key执行hash运算得到hashCode,然后再用一个数(数组长度 - 1)与hashCode进行”&“操作,得到的是一个介于0到数组长度-1的值,从而得到hash表中指定位置的值。


image.png

2)然后再遍历链表结构根据hashCode和key来匹配节点,之后再执行赋值操作(e.value = value)。


截图的是1.8的代码,重点在梳理逻辑~

扩容机制:

初始tab长度16,控制阈值16 * 0.75 = 12,每次tab插入新值的时候size会+1,同时和阈值比较,如果大于等于阈值了,tab就扩容一倍,同时阈值也重新用0.75计算(跟着放大一倍)。

然后需要做的是节点重排,把原先的所有节点重新计算hash位置,再重新插入。这里就会产生死循环链表的问题。

HashMap(jdk1.8)

数据结构:

数组+链表+红黑树

HashMap的插入过程:

与1.7基本流程基本一样,主要差别在于:插入的过程中会对链表的长度进行判断,如果超过了阈值(默认是8),则转换为一个tree结构


扩容机制:

初始tab长度16,控制阈值16 * 0.75 = 12,每次tab插入新值的时候size会+1,同时和阈值比较,如果大于等于阈值了,tab就扩容一倍,同时阈值也重新用0.75计算(跟着放大一倍)。

1.8和1.7相比节点重排做了个优化处理,通过e.hash & oldCap == 0来判断是否要把节点移动到新扩容的对应位置上。这样处理就解决了死循环链表的问题。

HashMap的设计并不是专门为多线程考虑的,不管1.7还是1.8在多线程resize的时候都会产生丢数据的问题。因此多线程处理并发情况的容器一定要使用ConcurrentHashMap。

参考内容:
什么是哈希表
HashMap中的tableSizeFor
hashMap的扩容机制详解
java容器之hashMap

你可能感兴趣的:(关于HashMap)