HashMap是最常用的Java数据结构之一,是一个具有常数级别的存取速率的高效容器。相对于List,Set等,结构相对复杂,本篇我们先对HashMap的做一个基本说明,对组成元素和构造方法进行介绍。
首先看HashMap的继承关系,比较简单,实现了Map和序列化等。
图1 HashMap继承关系图
HashMap继承自Map,Map作为一个重要的接口,很有必要需要介绍一下。
图2 Map接口
Map接口定义了一些通用方法,包括插入,删除,替换,遍历元素等常规集合方法。这里有必要重要关注的有:
1 Entry接口:
Entry
2 forEach方法:
default void forEach(BiConsumer super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch (IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
注意着是一个接口中的default方法。Java自1.8以后支持在接口中实现默认方法,不同于抽象方法,子类必须重新实现,default方法是开箱即用。这里可以看到,forEach方法是对Entry进行遍历并且执行指定操作。
/**
* Entry数组
*/
transient Node[] table;
/**
* Entry集合
*/
transient Set> entrySet;
/**
* Entry的数量
*/
transient int size;
/**
* HashMap被修改的次数
*/
transient int modCount;
/**
* 扩容阈值
*
*/
int threshold;
/**
* HashMap的装载因子
*
*/
final float loadFactor;
可以看到,这其中的变量和HashTable中基本一致,事实上,HashMap就是HashTable的去同步锁以及提升单节点效率的优化版。为何需要提生效率,一方面同步操作没有必要使用synchronisd这种重量级锁,另一方面,HashTable的设计方式可能会发生性能机具下降。
需要注意的变量关系是capability * loadFactor = threshold。翻译一下就是HashMap的扩容阈值是当前容量乘以承载因子。这个阈值不是table中的下标数量,而是整个HashMap已经装载的元素。
图2 HashTable出现极端Key碰撞
当HashTable的Key碰撞了以后,会在单一Node节点处形成单向链表。所以假设Key选取的不是很合适,冲突很多,HashTable就退化成LinkedList了,查找效率和插入效率都剧烈下降,这也背离了设计的初衷。
HashMap要如何解决这个问题呢,可以从下面定义的变量中一窥一二。
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* The bin count threshold for untreeifying a (split) bin during a
* resize operation. Should be less than TREEIFY_THRESHOLD, and at
* most 6 to mesh with shrinkage detection under removal.
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* The smallest table capacity for which bins may be treeified.
* (Otherwise the table is resized if too many nodes in a bin.)
* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
* between resizing and treeification thresholds.
*/
static final int MIN_TREEIFY_CAPACITY = 64;
为了避免出现HashMap变链表,HashMap引入了红黑树。红黑树是一种尽量保持平衡的搜索二叉树,简单而言对红黑树的增删改查都可以再O(lgn)时间内完成,较链表的O(n)有了巨大的提升,详细了解见红黑树维基百科。
这些变量也说明了链表与红黑树相互转化的条件:
1 当链表长度超过TREEIFY_THRESHOLD时,同时满足capacity大于MIN_TREEIFY_CAPACITY时,链表转化为树;
2 当树节点少于UNTREEIFY_THRESHOLD时,从树转化为链表。
HashMap的初始化构造方法有四个,都是围绕initialCapacity和loadFactor这两个变量展开的,由此可见这两个变量的重要性,最后一个是根据已有的Map集合初始化新的Map。
/**
* Constructs an empty HashMap with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
/**
* Constructs an empty HashMap with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs an empty HashMap with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
* Constructs a new HashMap with the same mappings as the
* specified Map. The HashMap is created with
* default load factor (0.75) and an initial capacity sufficient to
* hold the mappings in the specified Map.
*
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
*/
public HashMap(Map extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
到这儿了,基本把HashMap的关键元素介绍完了,接下来就是HashMap的具体实现了。那么HashMap究竟有哪些关键操作,并且是如何实现的,请看HashMap(Java8)源码解析(二)。