《精要主义》书中的一些感悟:
几乎没有事物的次要性,再怎么高估都不过分。试着利用精要主义打破用忙碌衡量成功的浅见吧。
无论是在工作or面试中,HashMap都是我们经常打交道的。所以今天我们源码的角度来窥探其奥妙。
1.HashMap的结构
1.1 JDK1.7版本
在JDK1.7以前的设计,HashMap采用数组+单链表的实现方式,链表就是用来处理Hash冲突的。
学过数据结构的人都知道,链表有个缺点:查找速度慢因为必须从头查询,时间复杂度是O(n)
1.2 JDK1.8版本
所以在JDK 1.8 对这里做了优化,因为我们知道树型结构可以将查找时间复杂度降为O(lg n)
在链表节点数量超过一定量(8)时,则将链表转化为红黑树, 以后找时间专门来了解一下红黑树。
1.3 关于HashMap结构的总结
HashMap的结构
JDK 1.7 : 数组 + 单链表 (注意这里强调一下单)
JDK 1.8 :数组 + 单链表 + 红黑树
1.(单)链表的目的:解决了Key的hash冲突问题;
2. 红黑树的目的:加快查询速度;
2. 设计HashMap
2.1 HashMap中的常量值
- 默认容量16;
- 最大容量2^30;
- 默认负载因子 0.75
- 可树化的阀值 8 (链表—> 红黑树);
- 去树化的阀值 6 (红黑树–>链表 );
- 哈希表考虑树形化的最小数量 64;
/**
* 默认的初始化变量(16)-必须是2的幂(次方)
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 最大容量(必须是2的幂且 <= 2的30次方)
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默认的装载因子(填充比例):0.75,即实际元素所占所分配的容量的75%时就要扩容啦。
* 如果该值过大,再增加元素将导致链表过长,查找效率降低;反之,浪费空间
* 关注内存:适当增大填充比;
* 关注查找性能: 适当减小填充比;
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 每个桶的可树化的阀值,即当桶中元素个数超过该值时,自动将链表转化为红黑树结构
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 每个桶的去树化阀值,即在扩容(resize())时,发现桶中的元素少于该值,则将树形结构还原为链表结构
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 哈希表中可被树形化的最小数量。
* 即若哈希表中的元素个数少于该值,则桶(bin)不会被树形化,而是会进行扩容(就算桶中的元素个数超过TREEIFY_THRESHOLD)。
* 主要是为了避免扩容&可树化的选择冲突
*/
static final int MIN_TREEIFY_CAPACITY = 64;
HashMap的跟容量相关的都必须是2的N次方。
2.2 HashMap中的变量
2.2.1 关键字-transient
用transient关键字标记的成员变量不参与序列化过程,即不会被序列化
2.2.2 HashMap中的变量
如果我们在设计HashMap时,这些基本我们都是要进行考虑的。(PS: 调整次数modCount 确实不知道有什么用, ~233333)
/***
* 我们都知道HashMap底层数据结构是:数组+链表
* 这个参数就是数组,即我们常说的:桶数组。
* table 在「第一次使用时进行初始化」并在需要的时候重新调整自身大小。对于 table 的大小必须是2的幂次方。
*/
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;
/**
* Map中的键值对的数量
*/
transient int size;
/**
* HashMap 进行结构性调整的次数。结构性调整指的是增加或者删除键值对等操作,注意对于更新某个键的值不是结构特性调整。
*/
transient int modCount;
/**
* The next size value at which to resize (capacity * load factor).
* Map的容量的阀值:表的大小 capacity * load factor,达到这个容量时进行扩容操作。
*/
int threshold;
/**
* The load factor for the hash table.
* 负载因子,默认值为 0.75
*/
final float loadFactor;
目前为止,我们只看到了HashMap结构中的桶数组: transient Node<K,V>[] table;
看链表其实就是看节点Node的。
2.3 HashMap链表节点
单链表
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
* Node 实现了Map.Entry接口
*/
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;
}
}
2.4 HashMap红黑树节点
注意这里继承的是LinkedHashMap.LinkedHashMapEntry<K,V>
LinkedHashMap.LinkedHashMapEntry<K,V> 是双向链表,本质上还是继承自Node的。
static final class TreeNode<K,V> extends LinkedHashMap.LinkedHashMapEntry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
/**
* Returns root of tree containing this node.
*/
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
...
}
这篇文章就到这里,干货貌似不多…
因为这篇文章的目的主要是让大家对于HashMap有个概括上的认识。
欢迎继续阅读下一篇 深入理解Java之HashMap —— 02