HashMap有几种常见构造方法:
//构造一个空的 HashMap ,默认初始容量(16)和默认负载系数(0.75)。
HashMap<Object, Object> hashMap = new HashMap<>();
//构造一个空的 HashMap具有指定的初始容量和默认负载因子(0.75)。
HashMap<Object, Object> hashMap1 = new HashMap<>(8);
//构造一个空的 HashMap具有指定的初始容量和负载因子。
HashMap<Object, Object> hashMap2 = new HashMap<>(8, 0.8f);
创建HashMap对象时 容量参数 和 负载系数的作用需要了解HashMap的底层数据结构及原理。
HashMap的底层数据结构为数组+链表,
插入数据时,会根据插入的key,计算其hash值,根据hash值映射到不同的数组索引,具体的映射为:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
比如我创建一个容量为4的HashMap,插入的key为k1,k2,k3。对应的value值为v1,v2,v3:
负载系数其实和容量有关系,假设容量为4,系数为0.75,则到数组的下标位置有 4*0.75=3 个区域填满则开始扩容,扩容时,容量变为先前的2倍,即8。这里的容量4不是指存储的元素为4个,而是数组的长度为4,扩容是也不是指存储的元素到达3个即开始扩容,而是数组的下标位置存储的元素为3个才开始扩容。假如上图在索引0的位置一直连接了10几个元素,但数组只有索引0和2的位置存在元素,没有达到3个,所以也不会扩容。在扩容时,会进行hash的重写散列,所以HashMap的容量一般都为2的次方。负载系数一般默认的0.75,这样会保证空间的利用率和效率平衡的最大化。假如系数过大,则当数组快满时才会扩容,会导致,一个数组下标下面可能会链接了多个元素,效率较低,但空间利用率较高,如果系数过小,这数组的容量还有很多时,就开始扩容,空间会浪费,但效率较高,因为元素直接存在数组下标处。所以一般负载系数默认即可。
容量为什么需要为2的次方,因为有很多:
LinkedHashMap是继承于HashMap的,底层数据结构为数组+链表,但是与HashMap不同的是LinkedHashMap维持了一个双向链表,将里面的元素通过双向链表链接起来以实现有序。构造方法与HashMap相比,多了一个
LinkedHashMap<Object, Object> linkedHashMap4 = new LinkedHashMap<>(8,0.8f,true);
其true代表的使用插入顺序,false代表的是访问顺序。
具体插入顺序和访问顺序有什么区别,我们先看看HashMap的元素遍历过程,再来看看LinkedHashMap两种排序方式
插入的键值对的顺序为 v1 - v2 - v3 - v4 - v5 - v6,遍历访问元素时,按照上图所述的方式进行遍历,从数组索引0开始遍历,假如下标处有多个元素组成一个链表就继续遍历链表上的元素,索引0上遍历完成之后,开始遍历索引1,…一直到最后一个索引遍历完成。因此上图遍历HashMap的顺序为 v1-v3-v6-v4-v2-v5。
LinkedHashMap中的每个键值对都是一个双向链表节点.在LinkedHashMap里面每个键值对比HashMap里面都多了两个属性before和after,用来链接前一个元素和后一个元素。LinkedHashMap里面还有一个head和tail属性,表示双向链表的头节点和尾节点。
LinkedHashMap里面大部分的方法都是HashMap中的方法,HashMap中预留了方法给其子类进行重写,如在put方法中,LinkedHashMap使用的还是HashMap的put方法,只是HashMap中put方法调用了newNode方法,而LinkedHashMap重写了其newNode方法,在方法中调用LinkNodeLast方法通过双向链表将元素关联起来。
假如插入的键值对的顺序为 v1 - v2 - v3 - v4 - v5 - v6,则头节点为v1,尾节点为v6,头节点的after链接到v2,v2的before链接到v1,以此类推,
所有linkedhashmap的插入顺序就是v1 - v2 - v3 - v4 - v5 - v6,遍历时也是以此来循环的,而如果使用的是访问顺序,那么每次通过get,put等方法对元素进行操作的都会对元素之间的双向链表进行更改,如插入时的顺序是v1 - v2 - v3 - v4 - v5 - v6,而我又调用了一次get的获取了下V3的值,那么v3将跑到链表的尾部,再次访问时的顺序就变成了v1 - v2 - v4 - v5 - v6- v3,所以访问顺序是根据你对linkedHashMap的操作而时刻变化的,插入顺序不会变的,你插入是什么顺序,遍历是就是什么顺序。LinkedHashMap的访问顺序可以用来做LRU缓存,可以删掉最久一次没有使用的元素,即头元素。