十分钟就要深入理解HashMap源码,看完你能懂?我觉得得再多看一分钟,才能完全掌握!
[外链图片转存失败(img-C72neM5a-1568723061512)(https://upload-images.jianshu.io/upload_images/16826084-eccd9643ad48ebf2.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
终于来到比较复杂的HashMap,由于内部的变量,内部类,方法都比较多,没法像ArrayList那样直接平铺开来说,因此准备从几个具体的角度来切入。
桶结构
HashMap的每个存储位置,又叫做一个桶,当一个Key&Value进入map的时候,依据它的hash值分配一个桶来存储。
看一下桶的定义:table就是所谓的桶结构,说白了就是一个节点数组。
transient Node<K,V>[] table;
transient int size;
节点
HashMap是一个map结构,它不同于Collection结构,不是存储单个对象,而是存储键值对。
因此内部最基本的存储单元是节点:Node。
节点的定义如下:
class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
可见节点除了存储key,vaue,hash三个值之外,还有一个next指针,这样一样,多个Node可以形成一个单向列表。这是解决hash冲突的一种方式,如果多个节点被分配到同一个桶,可以组成一个链表。
HashMap内部还有另一种节点类型,叫做TreeNode:
class TreeNode<K,V> extends LinkedHashMap.Entry<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是从Node继承的,它可以组成一棵红黑树。为什么还有这个东东呢?上面说过,如果节点的被哈希到同一个桶,那么可能导致链表特别长,这样一来访问效率就会急剧下降。 此时如果key是可比较的(实现了Comparable接口),HashMap就将这个链表转成一棵平衡二叉树,来挽回一些效率。在实际使用中,我们期望这种现象永远不要发生。
有了这个知识,就可以看看HashMap几个相关常量定义了:
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
- TREEIFY_THRESHOLD,当某个桶里面的节点数达到这个数量,链表可转换成树;
- UNTREEIFY_THRESHOLD,当某个桶里面数低于这数量,树转换回链表;
- MIN_TREEIFY_CAPACITY,如果桶数量低于这个数,那么优先扩充桶的数量,而不是将链表转换成树;
put方法:Key&Value
插入接口:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
put方法调用了私有方法putVal,不过值得注意的是,key的hash值不是直接用的hashCode,最终的hash=(hashCode右移16)^ hashCode。
在将