HashMap学习笔记

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也是一种提升效率的方式

猜你喜欢

转载自blog.csdn.net/kcstrong/article/details/80526430