1.HashMap首先是一个Map,提供Map接口所定义的能力,主要包括:
a.key-value形式的数据存储集合
b.通过put写入、通过get获取、通过remove删除,具备完善的增删改查功能
c.提供单独的key集合及value集合获取
d.Map中含有一个内部类Entry,为Map集合的条目类型
2.HashMap的父类是AbstractMap,上文中所提到的基本Map功能均在该类中实现
3.HashMap重载或实现了父类的重要方法,以实现自己的存储结构,比如,put方法在父类中并没有实现,HashMap实现了该方法,get方法父类是循环遍历的方式获取Entry对象,HashMap改为自己的方式。
4.三个重要值:
a.capatity:HashMap的容量
b.loadFactor:负载因子
c.threshold:扩容临界值
谈一下这三个变量的作用及关系:capatity定义了该HashMap的最大容量,loadFactor为HashMap最大容量内可存储比例,大于该比例,则该HashMap需要扩容,扩容成功后,capatity为新的最大容量,threshold为前两个参数根据一定规则算出来的值。注释中写的为threshold = capatity * loadFactor;在JDK 1.8中,threshold为大于等于capatity的最小的2的幂,比如capatity==16,则threshold==16,而capatity==17,则threshold==32(原因是啥呢?)
5.几个重要的思想:
a.HashMap散列的索引获取:
首先,capatity的定义要定义为2的幂,扩容时容量扩至capatity*2,这样便于各种位运算的运行,在各种算法实现中,位运算的效率是很高的。HashMap的内部数据结构为一个连续数组,长度即为capatity。定义好capatity之后,新来的元素要放到那个位置呢,其索引值的计算方式为:(n - 1) & hash,n的值为capatity,n-1的值为全1的低位,位数取决于capatity的值,比如capatity为16,n-1即为1111,与hash取与,可以理解为hash对n相除取余数,但位运算的效率更高。该索引的获取为hash在capatity提供的存储能力内作散列分布的方式。而且分布较为均匀,理论上可以做到全分布。为什么这么说的,还以16为例,不选15,选14,则进行与位运算的数为1110,未位为1的索引值是不会有元素可以存入,1、3、5、7、9、11、13、15,Hash散列的能力下降一半,碰撞率提升一倍。
b.HashMap的存储方式:
新插入的值,拿到索引之后,看该位置是否有元素,没有元素,则直接插入该位置,否则,进行排重(看该位置的所有元素是否已有相同的key,有的话修改为新值),没有重复key的情况下,以链表的方式将新值续至队尾,在JDK 1.8的实现中,链表的存储方式进行了扩展,当链表长度达到一定值之后,用树存放。按上面所讲,提供的存储索引是够用的,为啥还会出现同索引的key呢,因为hashcode基本是不会碰撞的,但对capatity取余之后的值较为随机,可能存在碰撞的情况。
6.最重要的三个方法:
a.put方法
public
V
put(
K
key,
V
value) {
return
putVal(
hash
(key), key, value,
false
,
true
);
}
先来看hash函数
static final int
hash(Object key) {
int
h;
return
(key ==
null
) ?
0
: (h = key.hashCode()) ^ (h >>>
16
);
}
实现方式为key的哈希值h的低16位与自己的高16位异或,高16位于0异或(原理是啥)
putVal这个方法为插入新的条目执行的逻辑
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
)
// table为存放所有节点地址的数组,若长度为0,则需要扩容,resize为扩容方法,稍后讲述
n = (tab = resize()).
length
;
// 注意tab索引的生成方式为 (n -
1
) & hash,原理可以看上面
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))))
// 若有相同key不同value的情况,更新value
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
)
// -1 for 1st
// 超过一定值后,用树的方式存储
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
;
}
b.get方法
public
V
get(Object key) {
Node<
K
,
V
> e;
return
(e = getNode(
hash
(key), key)) ==
null
?
null
: e.
value
;
}
final
Node<
K
,
V
> getNode(
int
hash, Object key) {
Node<
K
,
V
>[] tab; Node<
K
,
V
> first, e;
int
n;
K
k;
if
((tab =
table
) !=
null
&& (n = tab.
length
) >
0
&&
// 计算索引
(first = tab[(n -
1
) & hash]) !=
null
) {
if
(first.
hash
== hash &&
// always check first node
((k = first.
key
) == key || (key !=
null
&& key.equals(k))))
// 寻找key
return
first;
if
((e = first.
next
) !=
null
) {
if
(first
instanceof
TreeNode)
// 在树中遍历
return
((TreeNode<
K
,
V
>)first).getTreeNode(hash, key);
do
{
// 在链表中遍历
if
(e.
hash
== hash &&
((k = e.
key
) == key || (key !=
null
&& key.equals(k))))
return
e;
}
while
((e = e.
next
) !=
null
);
}
}
return null
;
}
c.resize方法--扩容方法
final
Node<
K
,
V
>[] resize() {
Node<
K
,
V
>[] oldTab =
table
;
// 计算新的capatity和新的threshold -- start
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
;
// double threshold
}
else if
(oldThr >
0
)
// initial capacity was placed in threshold
newCap = oldThr;
else
{
// zero initial threshold signifies using defaults
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;
// 计算新的capatity和新的threshold -- 完毕
@SuppressWarnings
({
"rawtypes"
,
"unchecked"
})
Node<
K
,
V
>[] newTab = (Node<
K
,
V
>[])
new
Node[newCap];
// table指向新的内存区
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
{
// preserve order
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;
}
由上面方法可知,扩容处理起来还是较为复杂的,因此,选择合适的capatity也是一种提升效率的方式