HashMap的方法分析
我们用的最多的应该就是put方法了,那我们就先来分析一下HashMap的put方法。
先看源码:当我们要放入一组(key,value)到HashMap中时,到底经历了怎样的过程。
public V put(K key, V value){
if(key==null) //如果key为null,则转到putForNullKey方法,大家注意一下这里只传进去了一个参数value
return putForNullKey(value) ;
int hash=hash(key.hashCode()); //通过hash方法对key的hashCode()进行二次加工。减少碰撞
int i=indexfor(hash,table.length); //i为这个key所对应table的位置,由上面的hash和table的大小决定
for (Entry<K,V> e=table[i];e!=null;e=e.next){ //开始遍历这个key所对应的table,寻找这个key是否已经存放过
Object k;
if(e.hash==hash &&((k=e.key)==key ||key.equals(k))){ //如果当前位置的key和传入的key相同
V oldValue=e.value; // 这是旧的value,也就是上次存放的这个key对应的值
e.value=value; //将value值更新成这次的
e.recordAccess(this); //记录一下
return oldValue; //将旧的value值返回
}
} //modcount与线程安全有关,在使用迭代器遍历HashMap时,一旦modCount发生变化就抛出异常
modCount++; //这些代码是在上面的方法中没找到key时调用的,即现在存放的key为第一次出现
addEntry(hash,key,value,i); //将相应的参数放到相应的位置
return null; //之前没有存在过这个key时返回一个null
}
现在我们来看一下上面调用的几个方法。
putForNullKey(),这是在放的key值为null时调用的方法。
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) { //可以看到直接选择了table[0]的桶,即第一个桶
if (e.key == null) { //对于key为null的存放方法,省去了计算hash的过程
V oldValue = e.value; //因为在HashMap中第一个table就是专门用来key为null的(key,value)对
e.value = value; //table[0]最多只能存放一个value,因为e!=null进入if(e.key==null)恒成立
e.recordAccess(this); //返回上次存放的value值,并将这次的value放入table[0]中
return oldValue;
}
}
modCount++; //以前没有存在过key为null时,直接将(null,value)放入第一个table中。
addEntry(0, null, value, 0);
return null;
}
再看一下addEntry()方法是如何存放(key,value)的。
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex]; //首先选中对应的table的位置,上面已经计算过table的位置
table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //调用Entry<K,V>的构造方法传入参数
if (size++ >= threshold) //如果放入这对键值对之后 HashMap的元素超过threshold(我的上一篇文章讲的最大容量)
resize(2 * table.length); //则扩容(上一篇文章也详细讲过扩容)
}
put方法介绍的差不多了,我们来看一下get方法:
public V get(Object key) {
if (key == null) //先判断key值是否为null,是的话跳转getNullKey()方法
return getForNullKey();
int hash = hash(key.hashCode()); //接下来找到key对应的table的位置
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) { //一个for循环寻找key对应的table处是否存在这个key,存在就返回value值
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null; //不存在就返回null
}
再来看一下判断是否存在某个元素containsKey()方法
public boolean containsKey(Object key) {
return getEntry(key) != null; //直接调用getEntry()方法,如果getEntry()方法返回不为null就代表存在该key
}
final Entry<K,V> getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode()); //判断key是否为null,需要特殊处理
for (Entry<K,V> e = table[indexFor(hash, table.length)]; //常规先找到所属table的位置
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) //返回不为null
return e;
}
return null; //没找到的话就返回null
}
这几个方法都没什么特别的地方,只是要对key为null的做特殊处理。
containsValue()方法时遍历整个table数组,寻找是否有匹配的value
public boolean containsValue(Object value) {
if (value == null)
return containsNullValue();
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (value.equals(e.value))
return true;
return false;
}
private boolean containsNullValue() {
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (e.value == null)
return true;
return false;
}
remove()方法
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key); //调用removeEntryForKey()方法删除指定的key
return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length); //先找到对应的table位置
Entry<K,V> prev = table[i];
Entry<K,V> e = prev; //同一个table处的元素时按照链表的形式相连(超过8个转化为树结构提升效率)
//删除指定元素就和删除链表中指定节点一样
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
//一直遍历该table处形成的链表直到找到
modCount++;
size--;
if (prev == e) //如果key所处的位置为链表首,直接将链表首改为next(下一个元素) table[i] = next; else //如果不是链表首,就将前一个元素与下一个元素相连,跳过这个key prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
最后讲一下clear方法
public void clear() {
modCount++;
Entry[] tab = table;
for (int i = 0; i < tab.length; i++)
tab[i] = null; //直接将每个table置为null,等待GC,释放空间
size = 0;
}
常用的方法基本介绍了,每次操作基本都需要考虑key为不为null两种情况。
还有一个特点就是modCount变量,这个变量与线程安全有关,当我们声明一个HashMap的Iterator时,
会将这个HashMap的modCount一并传入,每次迭代都需要检测modCount的值是否改变,改变的话就抛出异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
在ArrayList,LinkedList,HashMap等等的内部实现增,删,改中我们总能看到modCount,这是由于这几个类都是线程不安全的。
而一些线程安全的类则不需要modCount。