本文共 3197 字,大约阅读时间需要 10 分钟。
哈希表也称为散列表,是根据关键码值来确定数据的位置进行访问以提高查询速度,这种映射关系(函数)称为散列函数,存放数据的数组称为散列表。
它的数据存储形式是以键值对(key-value)的形式存储的,散列表的数据结构是数组+链表的形式。哈希表的节点内部类存放的有数据,指向下一个节点的引用,以及哈希码,这个哈希码的作用就是将这个节点和它的存放位置做一个对应关系,那么这个对应关系就是哈希函数。
比如哈希函数式为:f(x) = x mod 7,那么就是将该节点中的hash码模7,得到的结果就是该节点在散列表中的下标位置。
选择一个好的哈希函数,可以减少哈希冲突的发生,但是哈希冲突无法避免。
无论如何选择哈希函数,都无法避免哈希冲突,即同一个映射关系,不同的hash码会获得同一个散列表存储位置,要解决哈希冲突,有一种方法为链地址法,即如果发生哈希冲突,就将同一个位置的节点以链表的格式链在第一个节点后面,搜索时,如果同一个位置有多个节点,那么将搜索该链表,直到找到为止。
还有解决办法,如开放地址法,公共溢出法,再哈希法。HashMap类位于util工具包中。
它的父类是AbstractMap类,实现了Map,Cloneable(可克隆的),Serializable(可序列化的)接口。之后,进入else语句块,说明该下标有值,那么需要判断这个节点的下面连接的是树型结构还是链表结构,
如果p是树形结构(instanceof 关键字用来判断是不是类的实例),那么用putTreeVal()方法放入树结构中。 如果不是树形结构,就说明是链表结构,那么遍历这个链表, 直到p.next,即p的下一个节点为null时,尾插入链表,然后判断链表长度是否大于8,如果大于要转为树型结构。 之后需要对onlyIfAbsent参数进行操作,如果onlyIfAbsent为true,则不覆盖已经存在的值,否则覆盖存在的值,并把覆盖掉的值返回。 之后修改次数+1,并判断数组是否需要扩容。resize()函数:
首先获取原数组长度,如果长度大于0,即原数组不是null,再判断原数组长度长度是否大于允许的最大值1<<30,如果大于,说明这个数组不能再扩容了。否则,就2倍扩容原数组,阈值也双倍扩大。 进行临界值判断。 初始化新的table,之后涉及到一个重新哈希的过程,因为数组扩大了,那么在原来数组上发生的哈希冲突就得被缓解,所以要对发生哈希冲突的节点进行重新哈希,也就是重新定位他们在新数组中的存储位置。 首先判断节点的下一个节点有没有节点,如果没有节点,直接重新哈希, 如果有节点,分两种情况,一种是后面的节点是红黑树的节点, 一种是链表的节点,那么对它进行遍历,这里有一个细节,为什么 这里重新哈希后的节点位置只有两种位置,一种是j,也就是原来位置不变,一种是j+oldCap,也就是原来位置+原来数组长度?即下图为什么要判断(e.hash & oldCap) == 0? 这是因为假设oldCap为16,即10000,前面给它减了1,与上hash码得到0000,那么hash码的右数第5个位是不确定的,有可能是1,也有可能是0,现在不给oldCap减1即10000,与上hash码,如果还是0,那么说明第5个位是0(有0为0,无0为1),即重新哈希后位置不变,如果不是0,就给他加上oldCap,即10000,这样才满足oldCap-1 & hash为0,所以重新哈希的位置为oldCap+j。常用的HashMap迭代方式有三种。
1.通过Map.keySet()遍历key和value。 2.通过Map.entrySet()遍历key和value。 3.通过Map.values()遍历value,但是不能遍历key。在看HashMap的源码时,我们没有看到一个加锁的过程,那么就说明HashMap不是线程安全的,所以就有了ConcurrentHashMap,但是它也不是给每个方法都加上synchronized关键字,那样就成了HashTable。
其实ConcurrentHashMap采用了分段锁机制,它的内部存在一个Segment内部类
每个Segment内部是一个小型的HashMap结构。//待续
转载地址:http://mfwqi.baihongyu.com/