注:本文分析的是jdk1.8下的源码,不同jdk版本下的HashMapZipper方法会略有不同。在Java源码中,HashMap的实现可以归为拉链法。本文将根据Java源码分析jdk1.8实现的HashMap的各种细节。源代码分析/***默认初始容量-必须是2的幂。*/staticfinalintDEFAULT_INITIAL_CAPACITY=1<<4;//aka16/***最大容量,如果更高的值被隐式指定*由带参数的构造函数中的任何一个使用。*必须是二的幂<=1<<30。*/staticfinalintMAXIMUM_CAPACITY=1<<30;/***构造函数中未指定时使用的加载因子。*/staticfinalfloatDEFAULT_LOAD_FACTOR=0.75f;/***使用树而不是列表的bin计数阈值*bin。将元素添加到至少具有这么多节点的容器时,容器将转换为树。该值必须大于*2,并且至少应为8,以符合*树木移除中关于在收缩时转换回普通垃圾箱的假设。*/staticfinalintTREEIFY_THRESHOLD=8;/***bin计数阈值在*调整大小操作期间保持对(拆分)容器进行解树。应小于TREEIFY_THRESHOLD,并且至多6以在移除下使用收缩检测进行网格划分。*/staticfinalintUNTREEIFY_THRESHOLD=6;/***可将bin树化的最小表容量。*(否则,如果bin中的节点太多,则调整表的大小。)*应至少为4*TREEIFY_THRESHOLD以避免冲突*调整大小和树化阈值之间。*/staticfinalintMIN_TREEIFY_CAPACITY=6/***基本哈希bin节点,用于大多数条目。(*TreeNode子类见下文,其Entry子类见LinkedHashMap。)*/本源码中涉及到6个变量DEFAULT_INITIAL_CAPACITY变量含义:默认初始化容量变量值:16如果初始没有指定HashMap的容量HashMap<整数,整数>map=newHashMap<>();那么map的容量会被初始化为16需要注意的是建议在构造HashMap的时候指定size,如果指定的size不是2的整数次方,则改为2的整数次方大于这个值通过函数MAXIMUM_CAPACITY变量含义:最大容量变量值:1<<30HashMap的最大容量不是可以超过1<<30DEFAULT_LOAD_FACTOR变量含义:扩容负载因子变量值:0.75f当构造HashMap时没有指定容量,此时元素个数已经达到容量的0.75倍时,HashMap会扩容capacityTREEIFY_THRESHOLD,UNTREEIFY_THRESHOLD,MIN_TREEIFY_CAPACITY这三个变量需要一起引入,因为它们都涉及到JavaHashMap的底层实现。上面说到Java的HashMap可以归为拉链法的范畴,但是它的设计远比普通的拉链法简单。它非常脆弱。最简单的zipper方法是当哈希表发生哈希冲突时,将Node[]数组中冲突的Node连接到一个链表中,这样图中的形状就是Node[1]遇到哈希冲突时处理,它会将链表扩展到Node[1]节点。聪明的读者看到这里可能会有疑惑。如果发生大量哈希冲突,所有节点会不会变成一个很长的链表?这样一来,查询效率岂不是大大降低了?正是出于这个原因,Java在设计HashMap时改进了这种简单的拉链方式。当这个链表变得很长的时候,就会演化成一颗红黑树,而TREEIFY_THRESHOLD、UNTREEIFY_THRESHOLD、MIN_TREEIFY_CAPACITY这三个树类型变量就是用来控制红黑树和链表之间的转换的。TREEIFY_THRESHOLD的值为8,表示当链表长度大于8时,链表会变成红黑树。UNTREEIFY_THRESHOLD的值为6,表示当红黑树中的节点小于等于6时,会变回MIN_TREEIFY_CAPACITY的链表值为64,表示当发生哈希冲突时当哈希表容量小于64时,会优先选择扩容,而不是直接采用拉链的方式。综上所述,HashMap中的实现决定了它的检索和存储效率极高,时间复杂度可以认为是O(1)。当需要Key-Value存储且key唯一时,HashMap无疑是最好的选择。
