【Java面试题】HashMap中插入null key的过程

来源:互联网 发布:路由器转发原理 端口 编辑:程序博客网 时间:2024/06/04 20:01

本文转载自:http://blog.csdn.net/glory1234work2115/article/details/50825503



在HashMap中添加key==null的Entry时会调用putForNullKey方法

下面是HashMap的put方法:

[java] view plain copy
  1. public V put(K key, V value) {  
  2.        if (table == EMPTY_TABLE) {  
  3.            inflateTable(threshold);  
  4.        }  
  5.        if (key == null)  
  6.            return putForNullKey(value);  
  7.        int hash = hash(key);  
  8.        int i = indexFor(hash, table.length);  
  9.        for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  10.            Object k;  
  11.            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  12.                V oldValue = e.value;  
  13.                e.value = value;  
  14.                e.recordAccess(this);  
  15.                return oldValue;  
  16.            }  
  17.        }  
  18.   
  19.        modCount++;  
  20.        addEntry(hash, key, value, i);  
  21.        return null;  
  22.    }  
当key==null时就会调用putForNullKey方法

下面是putForNullKey的源码:


补充一下table:

transient Node<K,V>[] table;


[java] view plain copy
  1. private V putForNullKey(V value) {  
  2.         for (Entry<K,V> e = table[0]; e != null; e = e.next) {  
  3.             if (e.key == null) {  
  4.                 V oldValue = e.value;  
  5.                 e.value = value;  
  6.                 e.recordAccess(this);  
  7.                 return oldValue;  
  8.             }  
  9.         }  
  10.         modCount++;  
  11.         addEntry(0null, value, 0);  
  12.         return null;  
  13.     }  
直接去遍历table[0] Entry链表,寻找e.key==null的Entry或者没有找到遍历结束
如果找到了e.key==null,就保存null值对应的原值oldValue,然后覆盖原值,并返回oldValue

如果在table[0]Entry链表中没有找到就调用addEntry方法添加一个key为null的Entry

下面是addEntry的源码:

[java] view plain copy
  1. void addEntry(int hash, K key, V value, int bucketIndex) {  
  2.        if ((size >= threshold) && (null != table[bucketIndex])) {  
  3.            resize(2 * table.length);  
  4.            hash = (null != key) ? hash(key) : 0;  
  5.            bucketIndex = indexFor(hash, table.length);  
  6.        }  
  7.   
  8.        createEntry(hash, key, value, bucketIndex);  
  9.    }  
这个方法首先判断是否要扩容,当现在hashmap中的Entry数大于等于扩容临界值(capacity*load factor)并且index对应的地方没有Entry就扩容

hashmap每次扩容的大小为2倍原容量,默认容量为16,hashmap的capacity会一直是2的整数幂。

下面是resize扩容源码:

[java] view plain copy
  1. void resize(int newCapacity) {  
  2.        Entry[] oldTable = table;  
  3.        int oldCapacity = oldTable.length;  
  4.        if (oldCapacity == MAXIMUM_CAPACITY) {  
  5.            threshold = Integer.MAX_VALUE;  
  6.            return;  
  7.        }  
  8.   
  9.        Entry[] newTable = new Entry[newCapacity];  
  10.        transfer(newTable, initHashSeedAsNeeded(newCapacity));  
  11.        table = newTable;  
  12.        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);  
  13.    }  

关键是空表扩容后的拷贝到新表的方法transfer方法,下面是源码:

[java] view plain copy
  1. void transfer(Entry[] newTable, boolean rehash) {  
  2.        int newCapacity = newTable.length;  
  3.        for (Entry<K,V> e : table) {  
  4.            while(null != e) {  
  5.                Entry<K,V> next = e.next;  
  6.                if (rehash) {  
  7.                    e.hash = null == e.key ? 0 : hash(e.key);  
  8.                }  
  9.                int i = indexFor(e.hash, newCapacity);  
  10.                e.next = newTable[i];  
  11.                newTable[i] = e;  
  12.                e = next;  
  13.            }  
  14.        }  
  15.    }  

遍历原表table ,从table[0]开始,e=table[0],不为null就创建一个临时Entry next引用e.的下一个Entry,然后把e放到新表中,头插到table[i]中,i由indexFor方法决定i(h&newCapacity),然后让e=next,继续遍历拷贝。

扩容之后继续插入要插入的Entry,这个时候就要重新hash了,因为旧表已经扩容了,若果key为nul任然是0。

然后进行真正的插入,调用  createEntry(hash, key, value, bucketIndex),下面是源码:

[java] view plain copy
  1. void createEntry(int hash, K key, V value, int bucketIndex) {  
  2.         Entry<K,V> e = table[bucketIndex];  
  3.         table[bucketIndex] = new Entry<>(hash, key, value, e);  
  4.         size++;  
  5.     }  
进行头插,创建一个新的entry,new Entry<>(hash,key,value,e)

[java] view plain copy
  1. /** 
  2.         * Creates new entry. 
  3.         */  
  4.        Entry(int h, K k, V v, Entry<K,V> n) {  
  5.            value = v;  
  6.            next = n;  
  7.            key = k;  
  8.            hash = h;  
  9.        }  

新的entry复制到table[bucketIndex],并next引用原来的table[bucketIndex],完成。

在这里key是null,所以bucketIndex=0,现在的table[0]就是null key Entry。

如果不需要进行扩容,直接在table[0]进行头插就可以了。

总结:

上面分析了null key的插入过程,其实也包括了所有键值插入的过程。

1.先在table[0]的链表中寻找null key,如果有null key就直接覆盖原来的value,返回原来的value;

2.如果在table[0]中没有找到,就进行头插,但是要先判断是否要扩容,需要就扩容,然后进行头插,此时table[0]就是新插入的null key Entry了。



原创粉丝点击