集合体系的源码中,Map中的HashMap的设计堪称最经典,涉及数据结构、编程思想、哈希计算等等,在平常开发中对于一些源码的思想进行参考借鉴仍是颇有必要的。java
API体系git
在整个Map和Set的API体系中,最重要的就是HashMap的实现原理:github
因此Map和Set的系列中,除特殊API以外,基本原理都依赖HashMap,只是在各自具体实现时,适用于不一样特色的元素管理。算法
在看HashMap以前,先理解一种数据结构:数组+链表的结构。数据库
基于数组管理元素的位置,元素的存储造成链表结构,既然是链表那么就能够是单双向的结构,这须要针对具体的API去分析,经过这个结构能够获得几个关键信息:编程
既然上面简单描述了数组+链表的结构,那么从源码角度看看是如何封装的:segmentfault
transient Node<K,V>[] table;
在HashMap中数组结构的变量命名为table(表),而且是基于Node<K,V>
的节点:设计模式
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; }
实现Map.Entry
接口,并定义节点的结构变量,和节点自身的相关方法。数组
在知道HashMap中的基础结构后,能够看其相关的构造方法,初始化哪些变量:数据结构
无参构造
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; }
实际上还要关注一个核心参数:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 16
即数组默认的初始化容量DEFAULT_INITIAL_CAPACITY
为16,扩容的阈值loadFactor
为0.75,即表示当数组中元素达到12个便会进行扩容操做。
有参构造
固然也能够经过有参构造方法去设置两个参数:即容量和扩容的阈值:
public HashMap(int initialCapacity, float loadFactor) ;
经过两个构造方法的源码可知:当直接建立新的HashMap的时候,不会当即对哈希数组进行初始化,可是能够对关键变量作自定义设置。
顺着HashMap的使用方法,看元素添加:
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
在put的时候并无作过多直接操做,而是调用两个关键方法:
这里必须看一个关键方法,哈希值的计算:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
并非直接获取Object中hashCode的返回值,计算key对应的hashCode值,和hashCode值右移16位的值,并对两个结果进行异或运算,以此拉低哈希冲突发生的几率。
再看putVal()
方法,这里的操做就至关精彩:
核心步骤总结:
这里还须要说明一个问题:
HashMap基于红黑树来处理哈希冲突问题,若是hash冲突过多,对O(n)的查询性能的影响很是大,当冲突节点链表的冲突元素数量到达8时,而且数组的长度到达64时,会使用红黑树结构代替链表来处理哈希冲突的查询性能问题,关于树结构能够移步以前的相关文章。
容器在必定边界内能够不断添加元素,其核心的机制就是扩容,HashMap的扩容遵循最小可用原则,固然容量到达阈值,便会触发自动扩容机制。
阈值:threshold=capacity*loadFactor,默认即 16*0.75=12
。
核心方法:resize;
核心步骤总结:
很显然若是涉及数组扩容则会很影响效率,因此在平常开发中,能够在使用HashMap的时候预先估计好HashMap的大小,保证阈值大于存储的元素数量,尽量避免进行屡次扩容操做。
getNode
查找方法,经过hash值的计算,而后依次通过数组、红黑树、链表进行遍历查询:
removeNode
删除方法,首先经过hash值的计算,找到要删除的节点,而后判断索引位置是红黑树仍是链表结构,分别执行各自的删除流程:
这里对两个方法作个简单的说明:hashCode()
与equals()
,一般来讲重写equals方法的时候须要重写hashCode方法。
这两个方法均可以用来比较两个对象是否相等,可是hash值有存在冲突的状况,可能存在两个对象的hash值冲突,这时候能够经过equals判断对象值是否相同,==
判断值对象,地址判断引用对象。
在HashMap的结构中,链表上的hash值相同状况还要经过equals方法来判断具体值是否相同,才能找到相应的对象。
GitHub·地址 https://github.com/cicadasmile/java-base-parent GitEE·地址 https://gitee.com/cicadasmile/java-base-parent
阅读标签
【Java基础】【设计模式】【结构与算法】【Linux系统】【数据库】
【分布式架构】【微服务】【大数据组件】【SpringBoot进阶】【Spring&Boot基础】