HashMap
首先HashMap中维护了一个Entry(安吹)数组,数组中存储的数据类型是Entry,就是键值对,当每一个Entry要被添加到hashmap时,首先要使用hash算法计算key的hashcode,然后将hashcode进行取模操作,然后根据取模后的值来判断添加到数组中的那个位置,
数组了存储的元素是node,node 实现了map.Entry接口,
Node
node有四个属性值,并且重写了hashcode和equals方法
final int hash;//哈希
final K key;//key
V value;//value
HashMap.Node<K, V> next;//指向node的下一个节点,因为他可以被扩展成一个链表
HashMap
属性:
transient HashMap.Node<K, V>[] table; //node数组
transient Set<Entry<K, V>> entrySet; //用于存放map中所有的Entyr
transient int size; //元素数量
transient int modCount; //计数器,记录hashmap结构发生变化的次数(put、扩容)
int threshold; //size*负载因子
final float loadFactor; //负载因子
对象继承序列化接口后,肯能被序列化,但transient修饰符修饰后,将不会被自动序列化,用于修饰一些敏感的字段
put方法
注:在JDK1.8之前hashMap的插入是头插法,之后变成了尾插法。
1.判断table
是否初始化,没有初始化则调用resize()
为table
初始化容量,以及threshold的值
2…根据**table数组长度和哈希地址做&运算(i = (n - 1) & hash)**计算出该key对应的table
数组索引,
3.如果对应的数组索引位置没有值,则调用newNode(hash, key, value, null)
方法,为该键值对创建节点。
4.如果哈希值相等,key也相等,则是覆盖value操作。
5.如果根据哈希地址计算出该key对应的table
数组索引有节点,,但是节点的键key
和传入的键key
不相等,则遍历链表,如果遍历过程中找到节点的键key
和传入的键key
相等,则将对应的value
值更新。否则调用newNode(hash, key, value, null)
方法,为该键值对创建节点添加到链表尾部,如果追加节点后的链表长度 >= 8,则转为红黑树
7.最后所有元素处理完成后,判断是否超过阈值;threshold,超过则扩容。
get方法
1.table
不为空,且table
的长度大于0,且根据键key
的hashCode()
计算出哈希地址,再根据桶的数量-1和哈希地址做&运算计算出数组的下标,该下标下不为空(即存有链表头指针)则继续往下进行,否则返回null
。
2.如果和第一个节点的哈希地址、键key
都相同,则返回第一个节点。
3.如果第一个节点的下个节点不为空,则继续,如果第一个节点为树的节点,则执行getTreeNode(hash, key)
,在树中寻找节点,并且返回。否则遍历链表,找到键key
、哈希地址一样的则返回此节点
HashMap扩容
hashmap默认的初始长度是16,负载因子是0.75.当table中的元素被使用75%以上时,触发扩容操作,并且每次扩容一倍。
扩容时:将旧数组中的元素转换后,填充到新数组中。
因为我们使用的是2次幂的扩展(指长度扩为原来2倍),哈希值位与2^n-1就相当于取哈希值后n位,扩容时n+1了,所以就相当于取哈希值的n+1位,因此,我们在扩充HashMap的时候,不需要重新计算hash,。,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。
为什么要扩容一倍(为什么table的长度是2的次幂)?
当table的长度是2的次幂时,取模操作和位与运算结果相同
而取模操作比位与耗性能(因为位与运算直接对二级制进行操作,二取模要先将hash转化为10进制),所以可以用位与运算取代取模运算
为什么hash & (capacity - 1) == hash % capacity两者结果相同?
为什么X % 2^n = X & (2^n – 1)?
从2进制角度来看,X / 8相当于 X >> 3,即把X右移3位,此时得到了X / 8的商,而被移掉的部分(后三位),则是X % 8,也就是余数。而 X & (2^n – 1)这就是取X的后N位
为什么要右移16位再异或。
(h = key.hashCode()) ^ (h >>> 16)。把哈希值右移 16 位,也就正好是自己长度的一 半,之后与原哈希值做异或运算,这样就混合了原哈希值中的高位和低位,增大 了随机性。防止不同hashCode的高位不同但低位相同导致的hash冲突。简单点说,就是为了把高位的特征和低位的特征组合起来,降低哈希冲突的概率,也就是说,尽量做到任何一位的变化都能对最终得到的结果产生影响。
.为什么数组空间大于64之后再判断链表长度是否大于8再扩容,****小于64就只是做个扩容****
当负载因子为0.75时候,产生hash碰撞记录是最小的,此时Entry
单链表的长度几乎不可能超过8
。时间和空间的权衡
TreeNodes占用空间是普通Nodes的两倍,所以只有当bin包含足够多的节点时才会转成TreeNodes,而是否足够多就是由TREEIFY_THRESHOLD的值决定的。当bin中节点数变少时,又会转成普通的bin。并且我们查看源码的时候发现,链表长度达到8就转成红黑树,当长度降到6就转成普通bin。
多线程下扩容产生环形链表问题
待续~