前两篇学习了ArrayList和LinkedList的源码,一个是数组一个是双向链表,今天一起来看看HashMap(Jdk1.8)。
HashMap的数据结构是什么样呢
我们直接先看其put方法来看它是把数据放在什么结构内的。
这里我们看有一个hash(key),这里对key做hash计算:把key的hashcode()值与该值向右移动16位(空位补0)按位异或,hash计算决定了数据在HashMap数组结构中的存储位置。
putVal方法:
上图1的位置, Node<K,V>[]tab,Node?是不是有点熟悉,LinkedList的时候我们看到过,所以说HashMap的数据存储结构包含了数组和链表,是二者的组合结构。
和LinkedList的Node不同,HashMap的Node只有next指向它后一个元素,说明在HashMap数据结构内部采用的是单向链表。
那么问题来了,如果这个链表very long,那查询起来岂不是很伤
Jdk1.8大婶们已经优化了这个问题,看putval方法3的位置treeifyBin(),当链表长度大于8-1时,链表由线性树化,这是Java8中HashMap优化的地方通过红黑树二分查找加快查询速度。(红黑树推荐大神博客漫画:什么是红黑树?)
HashMap为何采用链表呢,数组不香嘛
HashMap中每一对key,value具体放在那个位置,这个索引是根据key的hash算出来的,数据千千万,后来的key计算出来的hash结果的存储地址有可能已经存储了数据,这就尴尬了,有矛盾就要解决啊。
解决哈希冲突的方法有:开发定址法(继续找下一块存储地址)、再散列函数法(采用另外的散列函数再次计算)、链地址法(数组+链表),HashMap就采用了链地址法。冲突的数据就绑一串吧。
数组?数组长度呢?扩容呢?
HashMap有四个构造函数,默认的容器长度是1<<4(16),当然和ArrayList一样我们可以传入容量的初始大小。
在无参构造初始化一个系统默认DEFAULT_LOAD_FACTOR=0.75f是什么鬼,负载因子,当长度达到最大容量的0.75时就需要扩容了,也就是说HashMap并不是满了才扩容。
HashMap扩容:容量扩大为原来的<<1(2)倍,然后创建一个新的Node数组,Jdk1.7是把老的数组遍历重新hash计算到新数组,Jdk1.8是判断原来的hash值新增的那个bit是1还是0,是0的话索引没变,是1的话索引变成“原索引+oldCap”。
HashMap使我们比较常用的数据结构,在多线程操作时它也是线程不安全的,需要线程安全使用HashTable和ConcurrentHashMap。