深入分析HaspMap源码

1.分析HaspMap的构造器

前面分析HashMap的put(K key,V value)源码的时候发现,其中有两个特殊的变量:

size:该变量保存了该HashMap中所包含的key-value对的数量。

threshold:该变量包含了HashMap能容纳的key-value对的极限,它的值等于HashMap的容量乘以负载因子(load factor)。

在HashMap的addEntry方法中,当size++>=threshold时。HashMap会自动调用resize方法扩充HashMap的容量。但每扩充一次,HashMap的容量就增大一倍。

HashMap源码中存在一个就table的数组。这个数组的长度其实就是HashMap的容量。HashMap包含如下几个构造器:

HashMap() 初始容量为16。负载因子为0.75的HashMap。

HashMap(int initialCapacity) 构建一个初始容量为 initialCapacity,负载因子为0.75的HashMap

HashMap(int initialCapacity,float loadFactor) 构建指定初始容量和负载因子的HashMap

当创建一个HasMap时,系统会自动创建一个table数组来保存HashMap中的Entry。
观察构造器的源码:

//以指定初始化容量,负载因子创建HashMap public HashMap(int initialCapacity, float loadFactor) { //初始容量不能为负数 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //初始容量不能太大 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //负载因子必须大于0 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); //计算出大于initialCapacity的最小的2的n次方值 int capacity = 1; while(capacity<initialCapacity) capacity<<=1; this.loadFacotor = loadFactor; //设置容量极限等于容量乘以负载因子 threshold = (int)(capacity * loadFactor); //初始化table数组 table = new Entry(capacity); init(); }

注意观察下面这两段代码:

int capacity = 1; while(capacity<initialCapacity) capacity<<=1;

找出大于initialCapacity的,最小的2的n次方值,并将其作为HashMap的实际容量。例如给定initialCapacity为10,那么HashMap的实际容量就是16。通常来说,HaspMap最后的实际容量通常比initialCapacity大一点,除非它刚好是2的n次方,所以我们在创建HaspMap需要指定容量时指定为2的n次方可以减少不必要的开销。

补充:

<< 把数据向左移动。移除的删除,右边用0补齐 相当于乘以2的移动次幂
>> 把数据向右移动。移除的删除 ,左边用最高位补齐 相当于除以2的移动次幂

2.HashMap根据key取出value

对于HashMap及其子类而言,它们采用Hash算法来决定集合中元素的存储位置。当系统开始初始化HashMap时,系统会创建一个长度为Capacity的Entry的数组。这个数组里可以存储元素的位置被称为”桶(bucket)”,每个bucket都有其特定的索引,系统可以根据其索引快速访问该bucket里存储的元素。

一般情况下,bucket里存储的是单个Entry,但也有会生成Entry链的情况(即两个key的hash值相同但equals返回false)。当bucket里面存储的是单个Entry,此时HaspMap性能最好。当程序需要根据key取出value时,只需要计算出key的hash值,再根据该hash值找出key在table数组中的索引,然后取出该索引处的Entry,最后返回该Entry的value即可。下面我们来看HashMapget(K key)的源码:

public V get(Object key) { //如果key是null。调用getForNullKey if (key == null) return getForNullKey(); //计算key的hash值 int hash = hash(key.hashCode()); //直接取出table数组中的指定索引处的值 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; //搜索下一个Entry e = e.next) { Object k; //如果该Entry的key与被搜索key相同 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }

从上面代码可以看出,当HashMap的每个bucket里只有一个Entry,HaspMap可以快速从bucket里取出Entry。在发生”Hash冲突”的情况下,单个bucket里存储的不是一个Entry,而是Entry链,此时系统只能通过遍历每个Entry,直到找到搜索的Entry为止。

总结一下:

HaspMap在底层把一个KEy-Value当成一个整体Entry对象来处理。

HaspMap底层通过一个Entry[]数组来保存所有的key-value对。

对于每一个要存储的key-value,系统通过Hash算法确定其存储位置。

当需要取出一个Entry时,也会根据Hash值来找到其在数组中存储位置

再谈负载因子loadFactor:

HaspMap有一个默认的负载因子值0.75。这是时间和空间成本上的一种折衷:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/115eb12d6db0d841fe2108b76a156c2c.html