余生很长,学精学透。
常量
源码中定义的一些常量和方法都表示为静态变量,如下:
// 默认容积
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大容积
static final int MAXIMUM_CAPACITY = 1 << 30;
// 当存量达到容积的0.75倍时,扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 当桶中bin的数量超过该阈值时,不再用链表存储,改用树存储(红黑树)
static final int TREEIFY_THRESHOLD = 8;
// 当桶中bin数量小于该值时,不再用树存储,改用链表存储
static final int UNTREEIFY_THRESHOLD = 6;
// 被树化时的最小容量
static final int MIN_TREEIFY_CAPACITY = 64;
计算哈希值的方法:
static final int hash(Object key){};
当然,还有一些信息是不希望被序列化的,用transient修饰了:
// 存储node节点的表
transient Node<K,V>[] table;
/**
* Holds cached entrySet(). Note that AbstractMap fields are used
* for keySet() and values().
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient int modCount;
主要常量这些,还有一些默认修饰的,如threshold等;
构造函数
一共有4种。
/*
* 传入容量和装载因子
*/
public HashMap(int initialCapacity, float loadFactor) {}
/*
* 传入容量,装载因子用默认的
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/*
* 容量和装载因子都使用默认的
*/
public HashMap(){}
/*
* 参数使用默认的,然后把注入的map一个个传给本HashMap
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
Node类
默认情况下,存在HashMap中的键值对都是以链表的形式存放在node类中。代码也并不复杂,如下:
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
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;
}
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;
}
}
里面就存放 了hash值、key、value和nextNode。
TreeNode类
实现比较复杂,继承了LinkedHashMap.Entry<K,V>,而后者是继承的HashMap.Node<K,V>,皮球绕了一圈又回到了Map.Entry<K,V>。红黑树比较复杂,先了解红黑树再看源码,红黑树的五大性质:
1)每个结点要么是红的,要么是黑的。
2)根结点是黑的。
3)每个叶结点,即空结点(NIL)是黑的。
4)如果一个结点是红的,那么它的俩个儿子都是黑的。
5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。
红黑树比较复杂,还没看懂……
在何种情况下,单向链表会转换成treenode?
在HashMap最开始的注释中,转换的情况是这样描述的:
* Because TreeNodes are about twice the size of regular nodes, we
* use them only when bins contain enough nodes to warrant use
* (see TREEIFY_THRESHOLD). And when they become too small (due to
* removal or resizing) they are converted back to plain bins. In
* usages with well-distributed user hashCodes, tree bins are
* rarely used. Ideally, under random hashCodes, the frequency of
* nodes in bins follows a Poisson distribution
* (http://en.wikipedia.org/wiki/Poisson_distribution) with a
* parameter of about 0.5 on average for the default resizing
* threshold of 0.75, although with a large variance because of
* resizing granularity. Ignoring variance, the expected
* occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
* factorial(k)).
我试着翻译一下:
鉴于TreeNodes一般都是有规则节点(链表节点?)的两倍长度,所以我们只有在节点足够多的时候才使用TreeNode。当节点数减少到足够小的时候,TreeNodes就会转换为普通节点(单向链表)。在使用的哈希值分布比较好的时候,很少使用树。理想情况下,在阈值为0.75时节点在箱中的分布遵循一个平均参数为0.5的泊松分布,虽然调整粒度但方差还是挺大的。忽略方差,期望分布满足这个公式。
好吧!这里只是说节点多的情况下会转换为树,还是没有写清楚具体啥时候转。既然前面已经说了,最开始是regular node,那说明插入数据达到一定程度后,就将表转为树,看put代码顺藤摸瓜,在putVal方法中有提现:
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
这里面,第一个if是判断是否哈希值、key都相同,就是判断是不是插入同样的数据;第二else if 判断是否已经是树了;第三个是正常插入操作,看代码可以看出来,是一个无限循环,直到找到节点p的next节点是空的,或者p的next节点就是这个插入的数据,如果p一直往next节点找,找到空并且箱数超过限定的转树阈值(默认是8),那就执行treeifyBin操作,即把table转换为红黑树。
所以,重点看看这个方法。
/**
* Replaces all linked nodes in bin at index for given hash unless
* table is too small, in which case resizes instead.
*/
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
从这个方法我们就可以看出,桶数组中,如果对应的某个桶的单向链路中bincount大于7,且桶的capacity大于64(均默认情况下),那么此链路就会转换为红黑树,而桶数组的其他单向链路则仍然是单向链路,不需要桶化。