一、概述
jdk8中hashmap的结构如图所示,它是由数组+链表+红黑树组成,jdk8之前是由数组+链表组成,那么为什么要这么做那?让我们带着疑问一步步往下看。首先看下这几个基本组成结构:
- 数组:数组存储结构是连续的,空间复杂度大,但查询的时间复杂度小。其寻址(通过下标搜索)效率高,一般的插入和删除效率低。
- 链表:链表存储结构是离散的,空间复杂度小。其寻址(通过下标搜索)效率低,一般的插入和删除效率高。
- 红黑树:作为一种二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。由于链表的查询时间复杂度为O(n),所以当链表很长时转换成红黑树将很好的提高效率!
二、源码分析
1、成员变量
//初始容量,默认为16,必须为2的指数倍
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//最大容量,必须为2的指数倍
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认负载因数
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//树化阈值,当桶中链表的长度大于8时将其转换为红黑树
static final int TREEIFY_THRESHOLD = 8;
//链表化阈值,当resize或删除操作时,桶中的元素数低于该值,将把红黑树转换成链表
static final int UNTREEIFY_THRESHOLD = 6;
//容器可以被树形化的最小容量,应该至少是4 * TREEIFY_THRESHOLD以避免调整大小和树状化阈值之间的冲突
static final int MIN_TREEIFY_CAPACITY = 64;
//存储元素的数组,第一次使用时初始化
transient Node<K,V>[] table;
//缓存entrySet
transient Set<Map.Entry<K,V>> entrySet;
//map的大小即map中包含的键值对的大小
transient int size;
//map被结构修改的次数,map被迭代时采用fail-fast机制
transient int modCount;
//扩容阈值,threshold = capacity * load factor
int threshold;
//当前负载因数,用来衡量hashmap空间占比情况,计算方法是size/capacity
final float loadFactor;
链表的结构代码 :
//为单向链表
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//两个节点只有键和值都相等时才返回true
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
红黑树结构代码,红黑树的讲解可以参考该博客红黑树详解:
/**
* Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
* extends Node) so can be used as extension of either regular or
* linked node.
*/
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {