一、域
1.1HashMap中默认参数化
默认值当我们构造HashMap没有指定参数时就会使用默认的DEFAULT _ INITIAL _ CAPACITY和默认DEFAULT _ LOAD _ FACTOR,以及我们的数据结构在什么时候发生转变的三个变量。
参数名 | 功能 |
---|---|
DEFAULT _ INITIAL _ CAPACITY | HasMap默认的bin的数量为* 16 ,HashMap采用数组+链表+红黑树的结构,所以该值表示默认的数组*长度;该值必须是2的次幂 |
MAXIMUM _ CAPACITY | HashMap默认情况下能够存放的键值对,一旦超过这个值,就会被设置为Integer.MAX _ VALUE |
TREEIFY_THRESHOLD | 当一个bin中的元素超过了这个值8,此时就会使用链表结构转换为红黑树的数据结构,用于向HashMap中添加key-value时的参数 |
UNTREEIFY_THRESHOLD | 当一个bin中的元素小于这个值6的时候,就会将bin的红黑树转换为链表结构的存储方式 |
MIN_TREEIFY_CAPACITY | 默认值为64确定一个bin中的数据结构,是否将链表转换为Tree的一个临界条件;如果map中的Entry的数量超过了MIN_TREEIFY_CAPACITY,那么此时一个bin中的元素如果超过了TREEIFY_THRESHOLD,那么此时数据结构会由链表转化为红黑树 |
1.2.1、table详解
一个table是一个Node类型的数组,而Node是一个链表的数据结构。因此我们HashMap最开始使用的是数组+链表的数据结构。如果我们在构造HashMap时没有指定初始容量,那么这个table数组的长度就会采用DEFAULT_INITIAL_CAPACITY这个值进行数组容量初始化。
1.2.2 、threshold详解
Hashtable resize 的门限值,一旦HashMap的size超过了这个threshold就需要对HashMap进行扩容。threshold = table.length*loadFactor.
1.2.3、loadFactor详解
HashMap的装载因子,我们一般都不会自己设定这个值,而是采用默认的DEFAULT_LOAD_FACTOR=0.75。根据Wikipedia我们可以知道loadFactor=map中存在的Entry的数量 / map中bin的数量。因此我们分析当loadFactor<1时和loadFactor>1时的情况。
- 当loadFactor<1的时候,这个时候表示map中实际的Entry数量是要小于map中bin的数量的,那么这个时候很少或者说不会出现hash冲突的情况,这个时候我们不管是执行put()方法还是执行get()方法都是O(1).但是我们这个时候需要的空间是要大于我们实际的Entry的数量的,因此这是一种用空间换时间来提高时间性能的方法。
- 当loadFactor>1的时候,这个时候表示map中实际的Entry数量是要大于map中bin的数量,那么这个时候就必然会出现多个Entry放在同一个bin当中的情况,也就是会出现Hash冲突的情况。那么这个时候我们put()和get()方法可能就会比O(1)的时间稍大。因此这是一种使用时间换空间的方案来提高空间性能的方法。
——————————————————————————————————————————————————————
二、 构造方法
HashMap有四个构造方法,我们这里主要介绍其中的三个。我们可以通过这三个构造方法选择设置loadFactor和initialCapacity
public HashMap(int initialCapacity, float loadFactor)//自己设定初始容量和装载因子
public HashMap(int initialCapacity)//初始容量为传递参数值,负载因子为0.75
public HashMap()// 初始容量为16,负载因子为0.75
三、 添加键值对
3.1、put(K key,V value)方法源码分析
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//步骤一:判断table是否为空或者长度为零,是则初始化Map,不是则转向步骤二
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//步骤二:如果key映射到table中对应位置的链表为空,那么我们新添加的键值对就作为头结点
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//步骤三:key-value存在与Map中则直接覆盖当前的value值,如果不成立转到步骤四
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//步骤四:如果当前bin的数据结构是红黑树,则直接插入key-value到Tree中;反之,则转向步骤五
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//步骤五:遍历链表,如果链表中Node.key存在与key相等的Node,那么此时就覆盖value;
//如果不存在,那么首先创建一个Node,然后将改Node添加在链表的末尾
//添加完节点后,需要判断滨bin中的Node数量是否超过了对应的,如果超过了则将链表的结构转换成红黑树,否则不执行
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); //将链表转换成红黑树,但是要考虑到总的HashMap的大小是否超过了Min_Treefy_Capacity
break;
}
if(e.hash == hash &&
((k = e.key)== key ||(key!= null && key.equals(k))))
break;
p = e;
}
}
if(e!= NULL){//
oldValue = e.value;
if(!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(E);
返回oldValue;
}
}
++modCount;
//步骤六:如果table中key-value的数量超过了规定的数量,那么此时就应该进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
-
3.2、执行步骤
-
步骤1、判断table是否为空或者长度为零,是则扩容;不是则跳过该步骤,转向步骤二
-
步骤2、如果key映射到table中对应位置的链表为空,那么我们新添加的键值对就作为头结点;如果该条件不成立则转向步骤三
-
步骤3、如果key映射到table中位置的链表(or 红黑树)的头结点的key相等,那么直接覆盖头结点的value值即可,如果不成立转到步骤四
-
步骤4、如果映射到table的中位值的头结点是TreeNode,那么调用putTreeVal方法插入键值对,如果此条件不成立则转向步骤五
-
步骤5、遍历链表,如果链表中Node.key存在与key相等的Node,那么此时就覆盖value;如果不存在,那么首先创建一个Node,然后将改Node添加在链表的末尾添加完节点后,需要判断bin中的Node数量是否超过了对应的,如果超过了则将链表的结构转换成红黑树,否则不执行
-
步骤6、如果map中key-value的数量size超过了threshold,那么此时就应该进行扩容
3.3、对于一个给定的key我们如果在table中找到其对应的位置?
主要使用下面两个步骤:
1、计算给定key的hashCode,将key的hashCode值与其高16位进行或运算得到key的hash值。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2、利用(table.length-1)&hash(key)就得到了key在table数组中的索引
(n - 1) & hash //在putVal()源码中步骤二中
四、扩容机制
final Node<K,V>[] resize() {
//步骤一:下面这段代码部分主要用于确定新的table的capacity和threshold
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
//如果之前的oldTable的容量已经不可扩充了,那么直接返回
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//newTable的容量首先扩容为之前的两倍,如果扩容之后比默认初始容量要大
//但是小于最大可扩容的容量,此时扩容的门限值也变成之前的两倍
//根据threshold = capacity*loadfactor,我们的loadfactor是不变的,因此newThr相应翻倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//如果table的容量已经初始化,那么
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//如果table未经初始化,那么我们table的capacity和threshold就先初始化
else {//零初始阈值表示使用默认值
newCap = DEFAULT_INITIAL_CAPACITY;
newThr =(int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if(newThr == 0){
float ft =(float)newCap * loadFactor;
newThr =(newCap <MAXIMUM_CAPACITY && ft <(float)MAXIMUM_CAPACITY?
(int)ft:Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({“rawtypes”,“unchecked”})
Node <K,V> [] newTab =(Node <K,V> [])new Node [newCap]; //步骤二:将oldTable的key-value重新散列到表中 table = newTab;
//遍历table数组
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
//如果oldTable的table[j]处只有一个节点
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
//如果oldTable的索引j处存储的是一个red-black tree
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
//如果oldTable的第table[j]条链表有多个节点的情况
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
//如果e.hash值小于oldCap,那么此时(e.hash & oldCap) == 0
//也就是说e.hash&(newCap - 1) = e.hash
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
//如果e.hash值小于oldCap,那么此时(e.hash & oldCap) == 1
//也就是说e.hash&(newCap - 1) = e.hash + oldCap
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
步骤总结:
1、首先判断我们的map的size是否已经达到了最大设定的值2的30次幂,如果是,那么我们调整threshold就可以了,否则调整
例1, oldCap = 16;hash(key1) = 8;newCap = 32
变量名 | 对应二进制 | &后的结构 |
---|---|---|
hash(key1) | 0000 0000 0000 0000 0000 0000 0000 1000 | |
oldCap-1 | 0000 0000 0000 0000 0000 0000 0000 1111 | 0000 0000 0000 0000 0000 0000 0000 1000 |
oldCap | 0000 0000 0000 0000 0000 0000 0001 0000 | 0000 0000 0000 0000 0000 0000 0000 0000 |
newCap-1 | 0000 0000 0000 0000 0000 0000 0001 1111 | 0000 0000 0000 0000 0000 0000 0000 1000 |
从这个例子我们可以看到,如果我们的oldCap=16,那么我们key1 用
也就是说如果我们的在扩容之前,如果key的hash值小于扩容之前的容量(oldCap),那么不管在扩容之前还是扩容之后key就会映射到与其hash值相同的位置
例1, oldCap = 16;hash(key1) = 17;newCap = 32
变量名 | 对应二进制 | &后的结构 |
---|---|---|
hash(key1) | 0000 0000 0000 0000 0000 0000 0001 0001 | |
oldCap-1 | 0000 0000 0000 0000 0000 0000 0000 1111 | 0000 0000 0000 0000 0000 0000 0000 0001 |
oldCap | 0000 0000 0000 0000 0000 0000 0001 0000 | 0000 0000 0000 0000 0000 0000 0001 0000 |
newCap-1 | 0000 0000 0000 0000 0000 0000 0001 1111 | 0000 0000 0000 0000 0000 0000 0001 0001 |
从这个例子我们可以看到,如果我们的oldCap=16,那么我们key1 用hash(key1)&(oldCap-1) + hash(key1) = Bin_index;也就是说如果我们的在扩容之前,如果key的hash值小于扩容之前的容量(oldCap),那么不管在扩容之前还是扩容之后key就会映射到与其hash值相同的位置
五、 红黑树
5.1红黑树的规则
- 根节点必须是黑色的
- 一个红节点的子节点必须是黑色的
- 黑节点的子节点可以是红色或者是红色的
- 一个null节点必须是黑色的
- 对于每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点
5.2红黑树的插入操作
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
x.red = true;
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
if ((xp = x.parent) == null) {//表示如果这个时候为根节点了,那么直接将根节点的颜色置为black(false)
x.red = false;
return x;
}
else if (!xp.red || (xpp = xp.parent) == null)//如果x节点的父节点不是红色(true),那么也就不会违反红黑树的性质
return root;
if (xp == (xppl = xpp.left)) {//如果p[x]是p[p[x]]的左节点,这是第一类大情况
if ((xppr = xpp.right) != null && xppr.red) {//如果p[p[x]].right是红色,那么直接将p[x]和p[p[x]].right改变颜色为black(false),那么将p[p[x]]向上递归直到满足红黑树条件
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {//如果p[p[x]].right为null或者颜色为black,那么这个时候我们就需要进行左旋或者右旋然后改变颜色
if (x == xp.right) {如果x是p[x].right,那么需要进行两次旋转,第一次左旋,接下来右旋
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {//如果x是p[x].left,那么我们只需要进行一次右旋就可以了
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateRight(root, xpp);
}
}
}
}
else {//如果p[x]是p[p[x]]的右节点,这是第一类大情况
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.left) {
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}