最近想起了之前看HashMap 源代码时写的那篇博客,突然发现一个问题,其实那篇写的很差,因为没有一个整体的思路,所以我决定重新写一篇HashMap相关的文章,顺便在有时间的时候会把数据结构的部分的HashMap的kotlin版本完成。
那么要说清HashMap的原理首先要弄清楚它是什么。
首先Hash Map是怎样的数据结构,使用过的人都知道,它是键值对一一对应的,每一个Key对应了一个value。所以Hash Map的每一个节点都应该是 包含一个key值和一个value值的。
HashMap的组成,最基础的HashMap中包含了一个数组,这个数组用来快速的查找数据,
Hash Map中 hash方法返回值为以存储的key的hashcode值的高16位的取值。
而hashMap 将key,value存入数组的过程原理,就是先将key通过hash方法计算返回的值和
数组的长度n减去1的得到的数值相位与((n-1)&hash(key) ,这个很好理解,位与计算是同为1才为1,所以得到的数值一定是一个小于或等于n-1的值。然后将这个结果值作为存取数据的数组索引。
由于不同hash值和n-1进行位与会得到相同的数值。所以两个不同的key可能会返回相同的结果,
当出现相同的结果的时,因此就需要在相同的索引的情况形成链表,因此hash Map的每一个节点都包含一个next值,用于在hash计算的值相同的情况形成链表。查找的时候,会先通过数组下标找到对应的Node。在遍历以这个Node为first节点的链表,直到找到key与传入的key相等的那个节点。返回它。
由于遍历链表的执行效率会随着链表的长度增加而增加,因此为了更高效。hashMap在这个链表超过长度8以后,将它重新排列成红黑树,因此可以推测Hash Map包含两种节点,或者也可以是相同的Node结构同时包含 next left right以方便进行不同方式的数据排列和遍历。从源码看hashmap使用了两种不同结构的Node ,Node与Tree Node。
那么要实现一个hashMap 只需要定义好Node类,在hashMap 中定义好数组,然后实现它的ha sh方法,还有put ,get方法。
那么Hash Map是如何扩容,并且在什么情况下扩容呢。当hashMap的size大于threshold默认是数组的长度乘以0.75时就会进行扩容。
扩容的时候,会重新创建一个数组,并将所有的数据重新计算hash的值,重新存到新的数组。
那么扩容以后是多大呢,在源码中是通过tableSizeFor返回的,可以看到注释里是原来长度的2次方,在源码中扩容时候是先通过 tableSizeFor给threshold赋值,之后再将数组的设置为threshold的值。而threshold的默认值,是默认的数组长度乘以0.75
/** * Returns a power of two size for the given target capacity. */ static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
整理好了思路,有空我会写一个Hash Map,补充到这篇博客。