传统 HashMap的缺点
(1)JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。
(2)当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。
(3)针对这种情况,JDK 1.8 中引入了红黑树(查找时间复杂度为 O(logn))来优化这个问题
TREEIFY_THRESHOLD
|
UNTREEIFY_THRESHOLD
|
MIN_TREEIFY_CAPACITY
|
---|---|---|
| static final int UNTREEIFY_THRESHOLD = 6 |
|
|
|
|
如果在创建HashMap实例时没有给定capacity、loadFactor则默认值分别是16和0.75。
当好多bin被映射到同一个桶时,如果这个桶中bin的数量小于TREEIFY_THRESHOLD当然不会转化成树形结构存储;如果这个桶中bin的数量大于了 TREEIFY_THRESHOLD
,但是capacity小于MIN_TREEIFY_CAPACITY
则依然使用链表结构进行存储,此时会对HashMap进行扩容;如果capacity大于了MIN_TREEIFY_CAPACITY
,则会进行树化。
package org.fan.learn.map;
import java.util.regex.Pattern;
/**
* Created by fan on 2016/4/7.
*/
public class MapKey {
private static final String REG = "[0-9]+";
private String key;
public MapKey(String key) {
this.key = key;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MapKey mapKey = (MapKey) o;
return !(key != null ? !key.equals(mapKey.key) : mapKey.key != null);
}
@Override
public int hashCode() {
if (key == null)
return 0;
Pattern pattern = Pattern.compile(REG);
if (pattern.matcher(key).matches())
return 1;
else
return 2;
}
@Override
public String toString() {
return key;
}
}
这个MapKey类用于作为HashMap的key的类型,实现了equals、hashCode、toString方法。其中,hashCode方法故意将所有数字字符串key的hash值返回1,其他字符串key的hash值返回2。
package org.fan.learn.map;
import java.util.HashMap;
import java.util.Map;
/**
* Created by fan on 2016/4/7.
*/
public class MainTest {
public static void main(String[] args) {
Map<MapKey,String> map = new HashMap<MapKey, String>();
/*
//第一阶段
for (int i = 0; i < 6; i++) {
map.put(new MapKey(String.valueOf(i)), "A");
}
*/
/*
//第二阶段
for (int i = 0; i < 10; i++) {
map.put(new MapKey(String.valueOf(i)), "A");
}
*/
/*
//第三阶段
for (int i = 0; i < 50; i++) {
map.put(new MapKey(String.valueOf(i)), "A");
}
*/
/*
//第四阶段
map.put(new MapKey("X"), "B");
map.put(new MapKey("Y"), "B");
map.put(new MapKey("Z"), "B");
*/
System.out.println(map);
}
}
第一阶段
这个时候桶中bin的数量小于TREEIFY_THRESHOLD
。
Debug如下所示:
看一下table中具体的存储方式:
第二阶段
这个时候桶中bin的数量大于了TREEIFY_THRESHOLD
,但是capacity不大于MIN_TREEIFY_CAPACITY
,则要扩容,使用链表结构存储。
从上图可以看出,1号桶中的存储结构依然是链表。
第三阶段
这个时候桶中bin的数量大于了TREEIFY_THRESHOLD
且 capacity大于了MIN_TREEIFY_CAPACITY
,因此,会树化。
对这个输出map的值,可以看到是乱序的,因为是使用树形结构进行存储的。
第四阶段
这个阶段主要是测试,如果一个桶采用了树形结构存储,其他桶是不是也采用树形结构存储。结论是,如果其他桶中bin的数量没有超过TREEIFY_THRESHOLD
,则用链表存储,如果超过TREEIFY_THRESHOLD
,则用树形存储。