hashmap数据结构详解(三)之hashcode实例及大小是2的幂次方解释
hashmap数据结构详解(五)之HashMap、HashTable、ConcurrentHashMap 的区别
在hashmap基础知识奠基之后,一起来走进hashmap的Java代码吧。
走进JDK源码:
以上是JDK中的接口与实现类。HashMap是继承于(抽象类)AbstractMap,AbstractMap实现了Map接口。下面截图我们看看一个实现类,一个接口里面具体干了什么。
HashMap内部也是由节点构成的。节点包括4个属性: HashMap的内部类 包含四个值(next,key,value,hash),其中next是一个指向Node的指针,key相当于上面节点的值 value对应要保存的值,hash值由key产生,hashmap中要找到某个元素,需要根据hash值来求得对应数组中的位置,然后在由key来在链表中找Entry的位置。HashMap中的一切操作都是以Node为基础进行的。HashMap的重点在于如何处理Node。因此HashMap中的操作大部分都是调用Node中的方法。可以说HashMap类本身只是提供了一个数组,和对Node类中方法的一些封装。
- 然后我们看一下HashMap的构造函数:
- 类中定义的静态变量:
initialCapacity:
就是前面所提的左边的数组大小(大小一般为2的指数,后面会解释),数组元素类型是Node类型。
loadFactor:
表示装填因子,解释:装填因子,以之前我们提到的绿色部分的0—9个空格为例。首先我们需要用hash函数进行第一次装载。此时如果在绿色空格中填入的越多,那么产生冲突的可能性就越大(冲突表示为:该绿色空格被占用,只用作为链表元素),假设绿色部分只使用了8个,那么装填因子为8/10.装填因为越小,那么相对来说产生冲突的可能性较小。
然后我们看看hashmap中几个最常用方法的实现(笔者用的JDK是1.8的,所以里面关于链表部分也存在节点树的应用):
- 最常用的put
代码:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
//hash值是散列值,由散列函数和key决定
Node<K, V>[] tab;//第一列的数组
Node<K, V> p;
int n, i;
if ((tab = table) == null || (n = tab.length) == 0)//为空则创建第一列数组
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);//创建一个新的节点,此节点是基槽部分即第一列的节点
else {
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) {//binCount表示节点数目
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st,大于了定义的8个节点数
treeifyBin(tab, hash);//桶的树形化,将之前的链表节点转换为红黑树节点
break;
}
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key,对于已有的键值,直接覆盖
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 超过最大容量 就扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
- 关于树形化的变量定义
TREEIFY_THRESHOLD 一个桶的树化阈值 | UNTREEIFY_THRESHOLD 一个树的链表还原阈值 | MIN_TREEIFY_CAPACITY 哈希表的最小树形化容量 |
当桶中元素个数超过这个值时,需要使用红黑树节点替换链表节点 | 当扩容时,桶中元素个数小于这个值,就会把树形的桶元素 还原(切分)为链表结构 | 当哈希表中的容量大于这个值时,表中的桶才能进行树形化 否则桶内元素太多时会扩容,而不是树形化 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD |
- 树形节点定义: