什么是 HashMap?
HashMap 是基于哈希表的 Map 接口的非同步实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
HashMap 的数据结构 在 Java 编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap 也不例外。HashMap 实际上是一个 “链表散列” 的数据结构,即数组和链表的结合体。
文字描述永远要配上图才能更好的讲解数据结构,HashMap 的结构图如下。
从上图中可以看出,HashMap 底层就是一个数组结构,数组中的每一项又是一个链表或者红黑树。当新建一个 HashMap 的时候,就会初始化一个数组。
下面先通过大概看下 HashMap 的核心成员。
publicclassHashMap<K,V> extendsAbstractMap<K,V> implementsMap<K,V>, Cloneable, Serializable{ // 默认容量,默认为16,必须是2的幂 staticfinalint DEFAULT_INITIAL_CAPACITY = 1<< 4; // 最大容量,值是2^30 staticfinalint MAXIMUM_CAPACITY = 1<< 30 // 装载因子,默认的装载因子是0.75 staticfinalfloat DEFAULT_LOAD_FACTOR = 0.75f; // 解决冲突的数据结构由链表转换成树的阈值,默认为8 staticfinalint TREEIFY_THRESHOLD = 8; // 解决冲突的数据结构由树转换成链表的阈值,默认为6 staticfinalint UNTREEIFY_THRESHOLD = 6; /* 当桶中的bin被树化时最小的hash表容量。 * 如果没有达到这个阈值,即hash表容量小于MIN_TREEIFY_CAPACITY,当桶中bin的数量太多时会执行resize扩容操作。 * 这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。 */ staticfinalint MIN_TREEIFY_CAPACITY = 64; staticclassNode<K,V> implementsMap.Entry<K,V> { //... } // 存储数据的数组 transientNode<K,V>[] table; // 遍历的容器 transientSet<Map.Entry<K,V>> entrySet; // Map中KEY-VALUE的数量 transientint size; /** * 结构性变更的次数。 * 结构性变更是指map的元素数量的变化,比如rehash操作。 * 用于HashMap快速失败操作,比如在遍历时发生了结构性变更,就会抛出ConcurrentModificationException。 */ transientint modCount; // 下次resize的操作的size值。 int threshold; // 负载因子,resize后容量的大小会增加现有size * loadFactor finalfloat loadFactor;}
HashMap 的初始化
publicHashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // 其他值都是默认值 }
通过源码可以看出初始化时并没有初始化数组 table,那只能在 put 操作时放入了,为什么要这样做?估计是避免初始化了 HashMap 之后不使用反而占用内存吧,哈哈哈。
HashMap 的存储操作
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
下面我们详细讲一下 HashMap 是如何确定数组索引的位置、进行 put 操作的详细过程以及扩容机制 (resize)
hash 计算,确定数组索引位置
不管增加、删除、查找键值对,定位到哈希桶数组的位置都是很