本文主要讲jdk1.8中HashMap原理。
HashMap的基本结构是数组+链表+红黑树。首先分析几个静态常量:
默认初始化容量为16,1<<4采用位运算效率更高。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
默认负载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
链表转红黑树的阈值
static final int TREEIFY_THRESHOLD = 8;
红黑树转回链表的阈值
static final int UNTREEIFY_THRESHOLD = 6;
开始转红黑树的最小容量
static final int MIN_TREEIFY_CAPACITY = 64;
下面分析几个构造方法:
无参构造都是默认值。
HashMap(int initialCapacity)构造方法,指定初始化map的容量,然后调用第一个构造方法;
HashMap(int initialCapacity, float loadFactor)构造方法,传入容量和加载因子,threshold属性代表初始化数组时的长度。
这里当传入initialCapacity时,会调用tableSizeFor()方法:
该方法对传入的值先-1,是为了避免传入值正好是2的幂,分析方法是对n进行右移位然后按位或运算
综上,这个方法正如注释所言,是为了得到大于传入值的最近的2的幂。
下面分析put方法:
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
里面先是调用的hash(key)方法:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
这里获得key.hashCode()的值,并将其高16位与低16位做按位异或运算,目的是为了降低hash碰撞的概率。然后返回一个int值。
然后分析putVal的具体流程,首先判断tab数组是否为初始数组,第一次put则调用resize()方法。
根据初始化HasMap的参数得到threshold的值,然后初始化了一个数组。默认是16,继续分析putVal过程,
p = tab[i = (n - 1) & hash]中(n-1)&hash会得到一个介于0~n-1的值,这个值就是数组的下标。
然后继续判断,当前传入的e是否是第一个节点(hash,key都相同),如果是就替换,否则就判断是否是树(红黑树后面章节单独讲),否则就是一个链表,然后循环这个链表,如果p.next是null,就新增Node,否则就去比较是否和传入值的hash,key相等,相等则替换,跳出循环,否则就继续循环直到新增节点,此时要判断链表长度是否超过阈值8,超过则转换成红黑树。
当数组分布到达threshold,首次默认12时,开始扩容(后面章节单独讲)
至此,put流程基本完成.