一,1.7HashMap
1.属性
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4 ;
static final int MAXIMUM_CAPACITY = 1 << 30 ;
static final float DEFAULT_LOAD_FACTOR = 0.75f ;
static final Entry< ? , ? > [ ] EMPTY_TABLE = { } ;
transient Entry< K, V> [ ] table = ( Entry< K, V> [ ] ) EMPTY_TABLE;
transient int size;
int threshold;
final float loadFactor;
transient int modCount;
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer. MAX_VALUE;
transient int hashSeed = 0 ;
2.构造方法
public HashMap ( ) {
this ( DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR) ;
}
public HashMap ( int initialCapacity) {
this ( initialCapacity, DEFAULT_LOAD_FACTOR) ;
}
public HashMap ( int initialCapacity, float loadFactor) {
if ( initialCapacity < 0 )
throw new IllegalArgumentException ( "Illegal initial capacity: " +
initialCapacity) ;
if ( initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if ( loadFactor <= 0 || Float. isNaN ( loadFactor) )
throw new IllegalArgumentException ( "Illegal load factor: " +
loadFactor) ;
this . loadFactor = loadFactor;
threshold = initialCapacity;
init ( ) ;
}
public HashMap ( Map< ? extends K , ? extends V > m) {
this ( Math. max ( ( int ) ( m. size ( ) / DEFAULT_LOAD_FACTOR) + 1 ,
DEFAULT_INITIAL_CAPACITY) , DEFAULT_LOAD_FACTOR) ;
inflateTable ( threshold) ;
putAllForCreate ( m) ;
}
3.inflateTable(初始化HashMap)
选取初始加载容量为:大于等于传入容量的2的幂次方。比如传入容量为10,那么实际初始化容量为16。
private void inflateTable ( int toSize) {
int capacity = roundUpToPowerOf2 ( toSize) ;
threshold = ( int ) Math. min ( capacity * loadFactor, MAXIMUM_CAPACITY + 1 ) ;
table = new Entry [ capacity] ;
initHashSeedAsNeeded ( capacity) ;
}
private static int roundUpToPowerOf2 ( int number) {
return number >= MAXIMUM_CAPACITY ?
MAXIMUM_CAPACITY : ( number > 1 ) ? Integer. highestOneBit ( ( number - 1 ) << 1 ) : 1 ;
}
4.Hash总值
使用hash总值的目的是:使原本的hash算法更加散列,需要加入虚拟机的配置。在jdk1.8的hashMap中,并没有该属性。
final boolean initHashSeedAsNeeded ( int capacity) {
boolean currentAltHashing = hashSeed != 0 ;
boolean useAltHashing = sun. misc. VM. isBooted ( ) &&
( capacity >= Holder. ALTERNATIVE_HASHING_THRESHOLD) ;
boolean switching = currentAltHashing ^ useAltHashing;
if ( switching) {
hashSeed = useAltHashing
? sun. misc. Hashing. randomHashSeed ( this )
: 0 ;
}
return switching;
}
5.Hash的计算方式
final int hash ( Object k) {
int h = hashSeed;
if ( 0 != h && k instanceof String ) {
return sun. misc. Hashing. stringHash32 ( ( String) k) ;
}
h ^= k. hashCode ( ) ;
h ^= ( h >>> 20 ) ^ ( h >>> 12 ) ;
return h ^ ( h >>> 7 ) ^ ( h >>> 4 ) ;
}
6.定位Hash桶的计算方式
定位Hash桶计算公式:h & (length-1)。
static int indexFor ( int h, int length) {
return h & ( length- 1 ) ;
}
7.put方法
如果底层数组为空,则会先初始化底层数组,默认容量为16。定位Hash桶位置,并且遍历该链表下的所有节点,如果有节点和插入节点的key相同,就覆盖该节点,并且返回旧的value,如果没有key相同,那么就会采用头插法,将该节点插入。 如果key为空,则执行空的逻辑,说明HashMap可以存放key为null的元素,该元素默认存放在数组下标为0的链表中。
public V put ( K key, V value) {
if ( table == EMPTY_TABLE) {
inflateTable ( threshold) ;
}
if ( key == null)
return putForNullKey ( value) ;
int hash = hash ( key) ;
int i = indexFor ( hash, table. length) ;
for ( Entry< K, V> e = table[ i] ; e != null; e = e. next) {
Object k;
if ( e. hash == hash && ( ( k = e. key) == key || key. equals ( k) ) ) {
V oldValue = e. value;
e. value = value;
e. recordAccess ( this ) ;
return oldValue;
}
}
modCount++ ;
addEntry ( hash, key, value, i) ;
return null;
}
8.ModCount属性
ModCount:表示修改次数,这是一种快速失败的机制,假设有一个线程在遍历hashmap,另一个线程在put或者remove,当它发现modCount != expectedModCount就会快速抛出异常ConcurrentModificationException,结束任务。
HashIterator ( ) {
expectedModCount = modCount;
if ( size > 0 ) {
Entry[ ] t = table;
while ( index < t. length && ( next = t[ index++ ] ) == null)
;
}
}
final Entry< K, V> nextEntry ( ) {
if ( modCount != expectedModCount)
throw new ConcurrentModificationException ( ) ;
Entry< K, V> e = next;
if ( e == null)
throw new NoSuchElementException ( ) ;
if ( ( next = e. next) == null) {
Entry[ ] t = table;
while ( index < t. length && ( next = t[ index++ ] ) == null)
;
}
current = e;
return e;
}
public void remove ( ) {
if ( current == null)
throw new IllegalStateException ( ) ;
if ( modCount != expectedModCount)
throw new ConcurrentModificationException ( ) ;
Object k = current. key;
current = null;
HashMap. this . removeEntryForKey ( k) ;
expectedModCount = modCount;
}
9.Hash桶的扩容
9.1 扩容源码
void addEntry ( int hash, K key, V value, int bucketIndex) {
if ( ( size >= threshold) && ( null != table[ bucketIndex] ) ) {
resize ( 2 * table. length) ;
hash = ( null != key) ? hash ( key) : 0 ;
bucketIndex = indexFor ( hash, table. length) ;
}
createEntry ( hash, key, value, bucketIndex) ;
}
void resize ( int newCapacity) {
Entry[ ] oldTable = table;
int oldCapacity = oldTable. length;
if ( oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer. MAX_VALUE;
return ;
}
Entry[ ] newTable = new Entry [ newCapacity] ;
transfer ( newTable, initHashSeedAsNeeded ( newCapacity) ) ;
table = newTable;
threshold = ( int ) Math. min ( newCapacity * loadFactor, MAXIMUM_CAPACITY + 1 ) ;
}
void transfer ( Entry[ ] newTable, boolean rehash) {
int newCapacity = newTable. length;
for ( Entry< K, V> e : table) {
while ( null != e) {
Entry< K, V> next = e. next;
if ( rehash) {
e. hash = null == e. key ? 0 : hash ( e. key) ;
}
int i = indexFor ( e. hash, newCapacity) ;
e. next = newTable[ i] ;
newTable[ i] = e;
e = next;
}
}
}
9.2 扩容的条件
扩容的条件为:((size >= threshold) && (null != table[bucketIndex])) ,即存放元素(包含链表)大于扩容阈值,并且定位hash桶位置首个元素不为null。
9.3 扩容的运行机制
hash桶的扩容默认为原来hash桶的2倍进行扩容,扩容之后旧的元素会被拷贝到新的hash桶中,复制到的位置会为两种,分别为:初始位置(旧的位置) 或者 初始位置 + 扩容量(也就是原来的hash桶的长度),复制也会采用头插法。所以,在复制之后,新的hash桶与旧的hash桶链表的顺序会相反。在多线程的情况下,可能会出现环形链表的情况,导致数据不安全。
9.4 扩容的优势
扩容不仅是提升数组长度,通过扩容可以将原来的链表变短,因为链表的hash会重新计算(hash & length -1),计算结果会出现两种情况,所以原本链表可能会被分配到两个位置,从而提升查找效率。
9.5 如何避免HashMap扩容
假设需要固定的30个容量(需要的容量为30)。
那么只需要让扩容阈值永远达不到30即可(扩容阈值 = 实际容量 * 加载因子)。 那么构造传入容量32,通过计算的实际容量也就为32,加载因子为1,那么hashmap只有达到32(扩容阈值)才会扩容,需要30个容量的话,就不会扩容。
二,1.8HashMap
1.put方法
执行流程:
判断数组是否为空,为空则先进行初始化数组,在jdk1.8中初始化数组的方法为resize()。 定位插入元素的下标位置,定位之后可能会走以下情况:
定位数组下标位置,第一个元素为空,则直接插入。 定位数组下标位置,第一个元素不为空,说明该下标位置已经变为了单向链表或者红黑树。
首先会判断,下标位置第一个元素是否与插入元素key相同,如果相同那么返回旧值,执行覆盖。 下标位置第一个元素与插入元素的key不相同,那么会分为以下两种逻辑:
节点类型属于树形节点,执行putTreeVal(),如果插入元素与树形节点的key相同,则返回旧值,执行覆盖。 节点类型属于普通节点,执行普通插入(尾插法),当下标位置下的元素个数超过8,那么在插入第9个元素时,链表会执行树化treeifyBin(),如果插入元素与单向链表节点的key相同,则返回旧值,执行覆盖。 如果运行到这一步,说明插入元素为第一次插入,没有与插入元素相同的key。那么会将修改次数+1。 判断HashMap中元素的数量是否大于扩容阈值,如果大于执行扩容。 返回null。
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;
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) {
if ( ( e = p. next) == null) {
p. next = newNode ( hash, key, value, null) ;
if ( binCount >= TREEIFY_THRESHOLD - 1 )
treeifyBin ( tab, hash) ;
break ;
}
if ( e. hash == hash &&
( ( k = e. key) == key || ( key != null && key. equals ( k) ) ) )
break ;
p = e;
}
}
if ( e != null) {
V oldValue = e. value;
if ( ! onlyIfAbsent || oldValue == null)
e. value = value;
afterNodeAccess ( e) ;
return oldValue;
}
}
++ modCount;
if ( ++ size > threshold)
resize ( ) ;
afterNodeInsertion ( evict) ;
return null;
}
2.resize方法
1.7与1.8resize方法的不同点:
1.7的resize方法只负责扩容,不负责初始化数组。1.8既要负责扩容,还要负责数组的初始化。 1.7旧数组上的链表迁移节点使用的是头插法。1.8中链表迁移节点使用的是尾插法。 1.7扩容的条件存放元素(包含链表)大于扩容阈值,并且定位hash桶位置首个元素不为null。1.8扩容条件为存放元素大于扩容阈值。 扩容的优势依然是使原来的链表或者红黑树节点上的元素变少,变得散列,提升查找效率。
final Node< K, V> [ ] resize ( ) {
Node< K, V> [ ] oldTab = table;
int oldCap = ( oldTab == null) ? 0 : oldTab. length;
int oldThr = threshold;
int newCap, newThr = 0 ;
if ( oldCap > 0 ) {
if ( oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer. MAX_VALUE;
return oldTab;
}
else if ( ( newCap = oldCap << 1 ) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1 ;
}
else if ( oldThr > 0 )
newCap = oldThr;
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] ;
table = newTab;
if ( oldTab != null) {
for ( int j = 0 ; j < oldCap; ++ j) {
Node< K, V> e;
if ( ( e = oldTab[ j] ) != null) {
oldTab[ j] = null;
if ( e. next == null)
newTab[ e. hash & ( newCap - 1 ) ] = e;
else if ( e instanceof TreeNode )
( ( TreeNode< K, V> ) e) . split ( this , newTab, j, oldCap) ;
else {
Node< K, V> loHead = null, loTail = null;
Node< K, V> hiHead = null, hiTail = null;
Node< K, V> next;
do {
next = e. next;
if ( ( e. hash & oldCap) == 0 ) {
if ( loTail == null)
loHead = e;
else
loTail. next = e;
loTail = e;
}
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;
}
3.treeifyBin方法
3.1 源码
final void treeifyBin ( Node< K, V> [ ] tab, int hash) {
int n, index; Node< K, V> e;
if ( tab == null || ( n = tab. length) < MIN_TREEIFY_CAPACITY)
resize ( ) ;
else if ( ( e = tab[ index = ( n - 1 ) & hash] ) != null) {
TreeNode< K, V> hd = null, tl = null;
do {
TreeNode< K, V> p = replacementTreeNode ( e, null) ;
if ( tl == null)
hd = p;
else {
p. prev = tl;
tl. next = p;
}
tl = p;
} while ( ( e = e. next) != null) ;
if ( ( tab[ index] = hd) != null)
hd. treeify ( tab) ;
}
}
3.2 转换规则和作用
转化规则:
如果数组为null,或者数组长度小于64,即使该链表上的节点已经大于8个,也不会将链表转换为红黑树。 只有数组索引位置不为null,并且数组长度大于等于64,链表长度大于8,才会将链表转换为红黑树。 作用:判断是否将链表转化为红黑树。
3.3 链表转换为红黑树的条件
链表转换为红黑树必须满足以下三个条件:
数组不为null。 链表上的节点数量大于8。 数组长度大于64。
3.4 转换过程
4.treeify方法
4.1 源码
final void treeify ( Node< K, V> [ ] tab) {
TreeNode< K, V> root = null;
for ( TreeNode< K, V> x = this , next; x != null; x = next) {
next = ( TreeNode< K, V> ) x. next;
x. left = x. right = null;
if ( root == null) {
x. parent = null;
x. red = false ;
root = x;
}
else {
K k = x. key;
int h = x. hash;
Class< ? > kc = null;
for ( TreeNode< K, V> p = root; ; ) {
int dir, ph;
K pk = p. key;
if ( ( ph = p. hash) > h)
dir = - 1 ;
else if ( ph < h)
dir = 1 ;
else if ( ( kc == null &&
( kc = comparableClassFor ( k) ) == null) ||
( dir = compareComparables ( kc, k, pk) ) == 0 )
dir = tieBreakOrder ( k, pk) ;
TreeNode< K, V> xp = p;
if ( ( p = ( dir <= 0 ) ? p. left : p. right) == null) {
x. parent = xp;
if ( dir <= 0 )
xp. left = x;
else
xp. right = x;
root = balanceInsertion ( root, x) ;
break ;
}
}
}
}
moveRootToFront ( tab, root) ;
}
4.2 作用
将链表调整为红黑树,并且调用moveRootToFront将红黑树的根节点调整为数组上的第一个元素。
4.2 定位父节点的过程
首先,判断的是当前节点与父节点的hash。 使用hash判断相等后,会查看该类的key是否实现Comparable接口:
实现Comparable接口:调用compareTo执行判断。 未实现Comparable接口,会执行3的判断。 调用tieBreakOrder判断:先会比较类名的长度,如果相等,那么会执行System.identityHashCode比较hashcode。
5.untreeif方法
5.1 源码
final Node< K, V> untreeify ( HashMap< K, V> map) {
Node< K, V> hd = null, tl = null;
for ( Node< K, V> q = this ; q != null; q = q. next) {
Node< K, V> p = map. replacementNode ( q, null) ;
if ( tl == null)
hd = p;
else
tl. next = p;
tl = p;
}
return hd;
}
5.2 作用
作用:hashMap扩容之后,数组位置为红黑树的节点可能会被拆分到两个位置(原位置,原位置+扩容量),对应位置的节点小于等于6(修复阈值)时,调用此方法将最初的红黑树转换为单向链表。
6.moveRootToFront方法
6.1 源码
static < K, V> void moveRootToFront ( Node< K, V> [ ] tab, TreeNode< K, V> root) {
int n;
if ( root != null && tab != null && ( n = tab. length) > 0 ) {
int index = ( n - 1 ) & root. hash;
TreeNode< K, V> first = ( TreeNode< K, V> ) tab[ index] ;
if ( root != first) {
Node< K, V> rn;
tab[ index] = root;
TreeNode< K, V> rp = root. prev;
if ( ( rn = root. next) != null)
( ( TreeNode< K, V> ) rn) . prev = rp;
if ( rp != null)
rp. next = rn;
if ( first != null)
first. prev = root;
root. next = first;
root. prev = null;
}
assert checkInvariants ( root) ;
}
}
6.2 作用和修复过程
作用:将数组上的节点替换为红黑树的根节点,并且修复双向链表。 修复过程:
将红黑树的根节点放到双向链表的第一个节点。 修复双向链表。 检查生成的红黑树是否有问题。
7.Split方法
7.1 源码
final void split ( HashMap< K, V> map, Node< K, V> [ ] tab, int index, int bit) {
TreeNode< K, V> b = this ;
TreeNode< K, V> loHead = null, loTail = null;
TreeNode< K, V> hiHead = null, hiTail = null;
int lc = 0 , hc = 0 ;
for ( TreeNode< K, V> e = b, next; e != null; e = next) {
next = ( TreeNode< K, V> ) e. next;
e. next = null;
if ( ( e. hash & bit) == 0 ) {
if ( ( e. prev = loTail) == null)
loHead = e;
else
loTail. next = e;
loTail = e;
++ lc;
}
else {
if ( ( e. prev = hiTail) == null)
hiHead = e;
else
hiTail. next = e;
hiTail = e;
++ hc;
}
}
if ( loHead != null) {
if ( lc <= UNTREEIFY_THRESHOLD)
tab[ index] = loHead. untreeify ( map) ;
else {
tab[ index] = loHead;
if ( hiHead != null)
loHead. treeify ( tab) ;
}
}
if ( hiHead != null) {
if ( hc <= UNTREEIFY_THRESHOLD)
tab[ index + bit] = hiHead. untreeify ( map) ;
else {
tab[ index + bit] = hiHead;
if ( loHead != null)
hiHead. treeify ( tab) ;
}
}
}
7.2 作用
作用:hashMap扩容后,将红黑树节点进行拆分,对拆分的两个链表进行恢复链表或者修复红黑树(根据修复阈值判断,默认为6),最终将两个链表分别插入到扩容后的数组中。
如果拆分后的链表长度小于等于6:调用untreeify将红黑树转换为链表。 如果拆分后的链表长度大于6:调用treeify修复红黑树。 注意:untreeify和treeify并不是一定会被调用的,根据拆分后的情况决定是否调用。