HashMap的底層的一些變量:java
transient Node<K,V>[] table; //存儲數據的Node數組 transient Set<java.util.Map.Entry<K,V>> entrySet; transient int size; //map中存放數據的個數,不等於table.length transient int modCount; //修改的次數,防止 int threshold; //臨界值 final float loadFactor; //擴展因子,通常狀況下threshold=table.length*loadFactor;
構造一個空的HashMap時,只有loadFactor被賦值爲默認的0.75。代碼以下:算法
public HashMapMmc(){ this.loadFactor=DEFAULT_LOAD_FACTOR; }
這裏我將介紹三個方法,put get remove,最後介紹entrySet()遍歷。數組
在調用put(key,value)方法時,底層調用的是這個方法:app
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) n=(tab=resize()).length; 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||(k!=null&&k.equals(key)))) 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) 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; }
這個方法有5個參數,第一個爲hash,能夠理解爲對key通過運算以後的一個值(具體算法:(key==null)?0:(h = key.hashCode())^(h>>>16)),第二個爲key,第三個爲value,這些都不用說了吧,第四個爲onlyIfAbsent,這裏表明的是是否覆蓋,若是爲false,一樣的key放在map中,後面放入的值會覆蓋原來的值,put方法在調用這個putVal()方法時,onlyIfAbsent寫死爲false的,因此HashMap中,是沒有重複的key值的,後來的value會覆蓋原來的value。看下面方法第四個參數:jvm
public V put(K key,V value ){ return putVal(hash(key),key,value,false,true); }
而後說放入過程:this
Node<K,V>[] newTab=(Node<K,V>[])new Node[newCap];
(n-1)&hash //n表明的是table的length,hash就是上面的第一個參數hash(key);
p.next=newNode(hash,key,value,null); //newNode方法會新聲明一個Node
2. get(Object key)方法:spa
知道了put方法,get(Object key)方法就比較簡單了,直接經過key算出他在table數組中的索引位置直接獲取就好了,由於有可能同一個索引位置放了幾個元素,因此他會先找到第一個元素,而後對比hash和key是否都相等。好比,在一個初始的table中,放入(33,"a"),(17,"b")。他們的hash分別爲33和17,key也分別爲33和17。當我調用get(17)時,先會根據17算出在table中的索引爲1,而後取出在這個索引中的第一個元素(33,"aa"),讓對比他們的hash和key是否都相等。顯而易見,第一個元素的key和hash都是33,而咱們想要get的hash和key都是17.因此不相等。那麼他就會去獲取第一個元素的next是否存在,若是存在會獲取出來在判斷hash和key是否都相等。debug
3. remove(Object key)方法:指針
和get(Object key)方法相似,先計算索引位置,找出這個索引位置的第一個Node命名爲p,在對比 p的key,hash和參數中的key,根據參數key計算出來的hash是否同樣,若是同樣那麼就在這個索引位置的值設爲null。若是在有碰撞的狀況下,就會與p.next作對比,若是同樣那麼p.next將指向這個p.next.next。而後這個元素沒有了指針也會就被jvm回收了。code
4.entrySet()方法:
我遍歷了一個HashMap看了看,由於想看看他是怎麼把碰撞的同一個索引位置的那麼多數取出了的,發現這個代碼不是很好理解,通過百度和本身猜想,有了一點了解。當時狀況是這樣的:
這個在代碼中是這樣的:調用entrySet方法來遍歷出一個個Map.Entry
for(Map.Entry<? extends K,? extends V> e:m.entrySet()){ K key=e.getKey(); V value=e.getValue(); }
entrySet()方法的代碼以下:
public Set<Map.Entry<K, V>> entrySet(){ Set<Map.Entry<K, V>> es; return (es=entrySet)==null?(es=new EntrySet()):es; }
這個entrySet是等於null的,也就是說每次都是new EntrySet();EntrySet類的代碼以下:
final class EntrySet extends AbstractSet<Map.Entry<K, V>>{ public final int size(){return size;} public final void clear(){HashMapMmc.this.clear();}
public final Iterator<Map.Entry<K, V>> iterator(){ return new EntryIterator(); } public final boolean contains(Object o){ if(!(o instanceof Map.Entry)) return false; Map.Entry<?, ?> e=(Map.Entry<?, ?>) o; Object key=e.getKey(); Node<K,V> candidate=getNode(hash(key),key); return candidate!=null&&candidate.equals(o); } public final boolean remove(Object o){ if(o instanceof Map.Entry){ Map.Entry<?, ?> e=(java.util.Map.Entry<?, ?>) o; Object key= e.getKey(); Object value=e.getValue(); return removeNode(hash(key), key, value, true,true)!=null; } return false; } public final Spliterator<Map.Entry<K, V>> spliterator(){ return new EntrySpliterator<>(HashMapMmc.this,0,-1,0,0); } public final void forEach(Consumer<? super Map.Entry<K, V>> action){ Node<K,V> [] tab; if(action==null) throw new NullPointerException(); if(size>0&&(tab=table)!=null){ int mc=modCount; for(int i=0;i<tab.length;++i){ for(Node<K,V> e=tab[i];e!=null;e=e.next) action.accept(e); } if(modCount!=mc) throw new ConcurrentModificationException(); } } }
看了EntrySet以後,感受new EntrySet()裏面不該該是空的嗎?怎麼可以遍歷出值來呢?
可是debug了下下面的這個e確實是有值的。最後查找了一下資料得出,加強性for循環內部是使用的iterator方法,又看了看果真EntrySet類中覆寫了iterator方法。返回的是一個new EntryIterator(),我又去找EntryIterator類,類裏就只有一個方法。而後又發現它繼承了HashIterator類,
這個類東西就多了。看下面的代碼:
for(Map.Entry<? extends K,? extends V> e:m.entrySet()){}
abstract class HashIterator{ Node<K,V> next; Node<K,V> current; int expectedModeCount; int index; HashIterator(){ expectedModeCount=modCount; Node<K,V>[] t=table; current=next=null; index=0; if(t!=null&&size>0){ //先入先進 do{}while(index<t.length&&(next=t[index++])==null); } } public final boolean hasNext(){ return next!=null; } final Node<K,V> nextNode(){ Node<K,V>[] t; Node<K,V> e= next; if(modCount!=expectedModeCount) throw new ConcurrentModificationException(); if(e==null) throw new NoSuchElementException(); if((next=(current=e).next)==null&&(t=table)!=null){ do{}while(index<t.length&&(next=t[index++])==null); } return e; } public final void remove(){ Node<K,V> p=current; if(p==null) throw new IllegalStateException(); if(modCount!=expectedModeCount) throw new ConcurrentModificationException(); current=null; K key=p.key; removeNode(hash(key),key,null,false,false); expectedModeCount=modCount; } }
能夠看出這個HashIterator迭代器的默認構造器中,會初始化一個next的變量,這個變量是在table數組中取得,索引是從0遞增的,即先入先出原則。構造初期會從0開始找有值的索引位置,找到後將這個Node賦值給next;而後要遍歷的時候是調用nextNode()方法,這個方法是先判斷next.next是否爲空,若是爲空繼續往上找有值的索引位置,若是不爲空就找next.next。這樣就能都遍歷出來了,是從索引0到table.length去一個個尋找遍歷的。
第一次寫本身的理解,但願多多指正!