HashMap

  认真了解了一下HashMap,之前都是一知半解,记录一下自己的理解之后再扩展补充。

  首先HashMap是一个类实现了Map接口,是集合的一种用来存储数据,HashMap是基于散列表的也叫哈希表,哈希表就是一种通过哈希函数将一个数映射到一个位置的东东,在HashMap中这个哈希表其实就是数组,当存储数据的时候,利用哈希算法得到键值对存储位置(首先对这个数据通过数据的key的HashCode方法计算出哈希值,然后通过高位运算和取模运算对哈希值做运算),然后看这个位置是否为空,如果为空的话就存储在数组里面,如果这个位置有对象,就用equals方法进行判断,判断要存入对象和这个位置的对象是否为同一对象,如果为同一对象就进行覆盖并返回旧的对象,如果结果为false那么就不是同一个对象,这个时候出现的现象叫做哈希冲突。

  那么怎么解决哈希冲突呢,常见的方法有开放定址法,就是说计算下一个可以存储的位置,还有就是链地址法,HashMap使用的是链地址法,链地址法主要基于的就是链表,数组的每一个位置其实可以叫做一个桶,每个桶存放一个Entry,就是一个键值对,HashMap底层有一个Entry类实现了Map.Entry接口,当出现冲突的时候这个Entry类里有一个next的属性可以指向下一个结点,形成链表,这个时候桶里装的就是链表,将冲突的对象存入链表,之后冲突的对象继续存入链表,这个时候链表最末端的数据是最初存入的对象。在jdk1.8以后当链表长度大于8的时候链表会转化为红黑树,提高查询速度,当长度小于6的时候再恢复为链表。遍历数组的时候如果数组元素挂着链表会将链表遍历完再遍历下一个数组元素。

  至于说的这个哈希桶就是每个key对应的链表就放在一个桶里,根据key先定位到桶的位置,再通过next指向下一个节点遍历链表找到对应的元素。

  细说树化的过程的话,很重要的一个属性就是MIN_TREEIFY_CAPACITY(默认是64),当链表长度也可以说是桶中节点数量小于TREEIFY_THRESHOLD(默认是8)的时候不会转化为红黑树,当链表长度大于8的时候会判断capacity是否大于MIN_TREEIFY_CAPACITY,如果小于不会转化为红黑树,会进行扩容,也就是说扩容除了当前数组容量大于临界值threshold会扩容还有这种情况也会扩容。如果capacity大于MIN_TREEIFY_CAPACITY并且链表长度大于8那么链表会转化为红黑树。

  然后说一下扩容,刚开始数组容量Capacity是为0的,当put对象的时候会初始容量为16,默认的加载因子LoadFactor为0.75,所以临界值为ThresHold=capacity*loadfactor,当数组的元素个数大于临界值时就进行resize扩容,扩容为之前容量的2倍形成一个新数组,然后通过hashcode方法重新计算数组元素在新数组中的位置,所以扩容很耗费性能,因为当冲突多了之后就需要增长链表,但是用链表查询效率很低,所以利用扩容增加查询效率,加载因子大的话表示数组元素占有率高,那数组容易发生冲突,所以查询性能低,内存空间浪费低,反之查询效率高,内存浪费严重。

  很坑的点就是jdk不同版本它有变化导致得分版本记忆,在发生冲突插入链中时,7是头插法,8是尾插法。在 resize 操作中,7需要重新计算下标的位置,而8不需要,只需要判断相应的位是0还是1,要么依旧是原下标,要么是oldCap+原下标。

原文地址:https://www.cnblogs.com/coderxiaobai/p/12483085.html