HashMap簡介
HashMap是一種K-V對映的一種資料結構,通過K(key)值能實現在O(1)的時間複雜度下找到對應的V(value)。JDK1.8之前,HashMap的底層資料結構是陣列+連結串列,陣列中的每個元素稱為一個Entry,包含(hash,key,value,next)這四個元素,其中連結串列是用來解決碰撞(衝突)的,如果hash值相同即對應於陣列中同一一個下標,此時會利用連結串列將元素插入連結串列的尾部,(JDK1.8是頭插法)。在JDK1.8及之後,底層的資料結構是:陣列+(連結串列,紅黑樹),引入紅黑樹是為了避免連結串列過長,影響元素值的查詢,因此當整體的陣列大小大於64時,並且連結串列的長度大於或等於8時,會把連結串列轉化成紅黑樹。在HashMap這一資料結構中,常見的方法有get、put等,在查詢或者插入元素時都會建立臨時陣列和指標。常見的Map有:HashMap、TreeMap、LinkedHashMap、HashTable、concurrentHashMap等。掌握HashMap對於面試時很有幫助的,這是面試常問的知識點。
static class Node<K,V> implements Map.Entry<K,V>{
//定義必要的屬性
final int hash;
final K key;
V value;
Node<K,V> next;
//初始化
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
//getKey
public final K getKey() {return key;}
public final V getValue() {return value;}
public final String toString() {return key + "=" + value;}
//重點,面試常備問道,求hashCode的值是key和value的異或
public final int hashCode(){return Objects.hashCode(key)^Objects.hashCode(value);}
//需要暫存原始值,最後再返回
public final V setValue(V newValue){
V oldValue = value;
value = newValue;
return oldValue;
}
//需要重寫equals,當key,value同時相等時,才相等
public final boolean equals(Object o){
if(o == this) return true;
if(o instanceof Map.Entry){
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
if(Objects.equals(key,e.getKey()) && Objects.equals(value,e.getValue()))
return true;
}
return false;
}
//hash是key值的hashcode高低16位異或,從這裡可以知道jdk1.8hashmap的key是可以為null
//若為null,取0,hashtable中的key是不能為null的
static final int hash(Object key){
int h;
return (key == null)?0:(h = key.hashCode()^(h>>>16));
}
//給出一個初始容量,會根據給定的初始容量,給出最近的2的多少平方
static final int tableSizeFor(int cap){
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMU_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
//同上,另一種實現方法,得到最近的2的多少的平方
static final int tableSizeFor(int cap){
int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
return (n < 0) ? 1 : (n >= MAXIMU_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
//其中numberOfLeadingZeros方法,採用了從大到小進行判斷
public static int numberOfLeadingZeros(int i){
if(i <= 0){
return i == 0 ? 32 : 0;
}
int n = 31;
if(i >= 1 << 16) {n -= 16; i >>>= 16;}
if(i >= 1 << 8) {n -= 8; i >>>= 8;}
if(i >= 1 << 4) {n -= 4; i >>>= 4;}
if(i >= 1 << 2) {n -= 2; i>>>= 2;}
return n - (i >>> 1);
}
//初始化HashMap
public HashMap(int initialCapacity, float loadFactor){
if(initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity:" + initialCapacity);
if(initialCapacity > MAXIMUM_CAPACITY){
initalCapacity = MAXIMUM_CAPACITY;
}
if(loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
this.loadFactor = loadFactor;
this.threshlod = tableSizeFor(initialCapacity);
}
//獲取特定key的value值
public V get(Object key){
Node<K,V> e;
return (e = getNode(hash(key),key)) == null ? null : e.value;
}
//通過hash、key獲取Node
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 && //總是先檢查第一個結點
((k = first.key) == key || (key != null && key.equals(k))))
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);//進行迴圈,一直比對hash、key是否相等
}
return null;
}
//利用getNode進行判斷
public boolean containsKey(Object key){return getNode(hash(key),key) != null;}
//put--(key,value)
public V put(K key, V value){
return putVal(hash(key),key,value,false,true);
}
//重點來看看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資料為空,進行擴容
n = (tab = resize()).length;
if((p = tab[i = (n - 1)&hash]) == null) //若找到下標,此時沒有值,即為null,則建立結點
tab[i] = newNode(hash,key,value,null);
else{//否則將將進行遍歷連結串列
Node<K,V> e; K k;
//先檢查頭結點,如果hash,key相等,則已經插入了該結點
if(p.hash == hash &&
((k = e.key) == key) || (key != null && key.equals(k)))
e = p;
//判斷插入的結點p是否是樹結點
else if(p isinstanceof 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);
//如果binCount>=7時,連結串列樹化為紅黑樹
if(binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab,hash);
break;
}//找到相等的結點,直接break
if(e.hash == hash && ((k = e.key) == key ||(key != null && key.equals(k))))
break;
//移動連結串列指標,指向下一個結點
p = e;
}
}
if(e != null){//存在對映關係,但是value為空
V oldValue = e.value;
if(!onlyIfAbsent || oldValue == value)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if(++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
final Node<K,V>[] resize(){
//定義oldTab,oldThr
Node<K,V>[] oldTab = table;
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 >= DEFALUT_INITIAL_CAPACITY)
newThr = oldThr << 1; // 閾值放大兩倍
}
//此時,oldCap等於零,但是閾值oldThr大於零,直接用oldThr進行替換
else if(oldThr > 0)
newCap = oldThr;//用thre替換初始容量
else {//此時,oldCap和oldThr都為零,進行初始值替換,表明第一次擴容
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACOR * DEFAULT_INITIAL)
}
//初始化閾值newThr,初始化threshold
if(newThr == 0){
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY)?(int)ft : Integer.MAX_VALUE;
}
threshold = newThr;
//建立Node型陣列,對table進行賦值
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//將oldTab中元素賦值給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{ //rehash,高位等於索引+oldCap,即:j + oldCap;
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;
}//表明需要從新hash到新的位置,同理如上
else{
if(hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
}while((e = next) != null);
if(loTail != null){ //先建立一個連結串列,之後將這個連結串列接到j索引處
loTail.next = null;
newTab[j] = loHead;
} // 同理只是更改了索引位置:j + hiHead
if(hiTail != null){
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
public V remove(Object key){
Node<K,V> e;
return (e = removeNode(hash(key),key,null,false,true)) == null ? null : e.value;
}
final Node<K,V> removeNode(int hash, Object key,Object value,
boolean matchValue,boolean movable){
//定義臨時變數
Node<K,V>[] tab; Node<K,V> p; int n, index;
if((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null){
Node<K,V> node = null, e; K k; V v;
//若是頭結點
if(p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
//往下遍歷
else if((e = p.next) != null){
if(p instanceof TreeNode)//是樹形節點
node = ((TreeNode<K,V>) p).getTreeNode(hash,key);
else {
do {//進行迴圈遍歷,找到即跳出迴圈
if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
node = e;
break;
}
p = e;
} while((e = e.next) != null);
}
}
if(node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))){
if(node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this,tab,movale);
//若判斷是頭結點,直接去掉頭結點,接在後面
else if(node == p)
tab[index] = node.next;
else
p.next = node.next;//在連結串列中間,跳過該節點
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
}