本篇内容是学习的记录,可能会有所不足。
一:JDK1.7中的HashMap
JDK1.7的hashMap是由数组 + 链表组成
1 | php复制代码 /** 1 << 4,表示1,左移4位,变成10000,即16,以二进制形式运行,效率更高 |
1:DEFAULT_INITIAL_CAPACITY,是hashMap默认的初始容量,它的大小一定是2的幂。
2:MAXIMUM_CAPACITY,hashMap支持的最大容量。
3:DEFAULT_LOAD_FACTOR,hashMap默认的负载因子,值为0.75,它决定hashMap数据的密度。
4:Entry<K,V>[] table,hashMap数组,可以根据自己的需要调整大小,长度一定是2的幂。
5:size,主要是记录hashMap中元素的数量。
6:threshold,调整hashMap后的值,即容量*负载因子。
7:loadFactor,可以调整的负载因子。
8:modCount,用来记录hashMap结构被修改的次数。
hashMap源码中有四个构造函数,初始化的时候可以知道容量和负载因子的大小。
1 | scss复制代码 /** 做了两件事:1、为threshold、loadFactor赋值 2、调用init() |
接下来看下put方法:
1 | ini复制代码 public V put(K key, V value) { |
可以看到到table是空的时候,调用了一个方法:
1 | scss复制代码private void inflateTable(int toSize) { |
这个方法用来初始化table和table的扩容,roundUpToPowerOf2可以保证hashMap的容量一定是2的幂。
hashMap put元素时,会先根据hash运算计算出hash值,然后根据hash值和table的长度进行取模,计算出元素在table中的下标,如果key相同就覆盖原来的旧值,如果不相同就加入链表中。
1 | arduino复制代码/** |
1 | scss复制代码 /** |
当put的元素个数大于12时,即大于hashMap的容量*负载因子计算后的值,那么就会进行扩容,上述源代码可以看到扩容的条件, 除了大于12,还要看当前put进table所处的位置,是否为null,若是null,就不进行扩容,否则就扩容成原来容量的2倍,扩容后需要重新计算hash和计算下标,由于table的长度发生了变化,需要重新计算。
接下来看下get方法:
1 | ini复制代码public V get(Object key) { |
get方法也是需要先计算hash然后计算下标,再去寻找元素。
)
二:JDK1.8中的HashMap
JDK1.8中的hashMap和1.7最大的区别就是引入了红黑树
1 | php复制代码/** |
1 | arduino复制代码/** |
下面看下put方法:
1 | scss复制代码 public V put(K key, V value) { |
可以看到,上述源代码中,put的时候加入了红黑树,当put元素时,若链表的长度大于8,即源代码中的TREEIFY_THRESHOLD的值,这个时候链表就会转化为红黑树结构;当进行扩容的时候,红黑树转移后,若元素个数小于6,那么就会重新转化为链表。
)
三:JDK1.7中的ConcurrentHashMap
JDK1.7中的ConcurrentHashMap和JDK1.7中的HashMap的区别就是数组所存的元素,我们知道ConcurrentHashMap 是线程安全的。
1 | ini复制代码public V put(K key, V value) { |
1 | swift复制代码/** segments中每个元素都是一个专用的hashtable |
可以看到1.7中的ConcurrentHashMap数组中所存的是segments,每个segments下都是一个hashTable。当put元素时,会加锁,然后计算hash和下标,计算下标会计算两次,一次是在数组中的segments的位置,一次是在hashTable的位置。
)
四:JDK1.8中的ConcurrentHashMap
JDK1.8中的ConcurrentHashMap和JDK1.8中的HashMap结构一样,只是在处理上有区别
1 | ini复制代码public V put(K key, V value) { |
当put元素时,会使用CAS操作,去判断数组中所要put到的位置元素是否为空,为空就修改为当前的put的元素,若CAS操作失败,那么会自旋,这个时候发现数组里已经有元素了,那么就会锁住链表或者红黑树头部,把元素放入链表或者红黑树下面 。
五:hash冲突
当put的时候需要计算hash和下标,这个时候计算出来的值可能存在一样的,那么存到数组中的相同位置,就会发生hash冲突,
计算出的hash值一样一定会发生hash冲突,但是hash值一样的概率很小,计算出的下标值是一样的概率很大,所以hash冲突主要是由下标位置一样引起的,hashMap的解决方式是使用链地址法,即使用链表的方式解决,key一样的时候才会覆盖,否则就把元素放到链表的下一个位置。
本文转载自: 掘金