HashMap常用方法分析

                                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。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值