Q1 HashMap是通過什麼方式實現的? - >數組Entry的鏈表
Q2 HashMap爲什麼不安全?怎樣實現安全?
多線程情況下如果兩個線程同時放的哈希(鍵)相同,則會發生碰撞,出現覆蓋的情況。
避免需要這樣實例:建議使用ConcurrentHashMap
Q3兩個鍵的hashcode相同,你如何獲取值對象?如何把對象?
3.1當我們調用get()方法方法,HashMap中會使用鍵對象的哈希碼找到桶位置,找到鬥位置之後,會調用keys.equals()方法去找到鏈表鏈表中正確的節點,最終找到要找的值對象。
3.2把時候先查找鍵的哈希值,如果哈希相同,那麼則找到該對象的鏈表,放在鏈頭的位置。
Q4調整HashMap的大小存在什麼問題嗎?
當多線程的情況下,可能產生條件競爭。
當重新調整HashMap的大小的時候,存在條件競爭,因爲如果兩個線程都發現HashMap中需要重新調整大小了,它們會同時試着調整大小。在調整大小的過程中,存儲在鏈表中的元素的次序會反過來,因爲移動到新的桶位置的時候,HashMap並不會將元素放在鏈表的尾部,而是放在頭部,這是爲了避免尾部遍歷(tail traversing)。如果條件競爭發生了,那麼就死循環了。這個時候,你可以質問面試官,爲什麼這麼奇怪,要在多線程的環境下使用HashMap的呢?:)
Q5爲什麼字符串,Interger這樣的包裝類適合作爲鍵?自定義對象可以作爲鍵嗎?
字符串是不可變的,也是最後的,而且已經重寫了equals()方法和hashCode()方法方法。
自定義對象可以作爲鍵,只要是插入之後是不可變的,並重寫了等於和hashCode()方法方法。
Q6 HashMap複雜度 - >理想O (N / Buckets),N就是以數組中沒有發生碰撞的元素
要保證O(N),需要儘量避免出現重複的的hashCode(鍵),因爲出現重複的回在獲得/放的時候需要進行二次等於比較計算出在鏈表的位置;以及鏈表的長度都會影響時間複雜度
如果某個桶中的鏈表記錄過大的話(當前是TREEIFY_THRESHOLD = 8),就會把這個鏈動態變成紅黑二叉樹(TreeMap),使查詢最差複雜度由O(N)變成了O( logn)時間。
Q7重新調整HashMap大小存在什麼問題嗎?
可能產生條件競爭,多線程的情況下會有多個線程同時修改地圖的大小,導致出現死循環。
Q8 HashMap&TreeMap&LinkedHashMap使用場景?
一般情況下,使用最多的是HashMap.HashMap
:在Map中插入,刪除和定位元素時;
TreeMap:在需要按自然順序或自定義順序遍歷鍵的情況下;
LinkedHashMap的:在需要輸出的順序和輸入的順序相同的情況下。
Q9 HashMap和HashTable有什麼區別?
①,HashMap是線程不安全的,HashTable是線程安全的;
②,由於線程安全,所以HashTable的效率比不上HashMap;
③,HashMap最多隻允許一條記錄的鍵爲null ,允許多條記錄的值爲null,而HashTable不允許;
④,HashTable爲11,前者擴容時,擴大兩倍,後者擴大兩倍+1;
⑤,HashMap需要重新計算哈希值,而HashTable直接使用對象的hashCode
1地圖是一個接口。
//map是一個接口
public interface Map<K,V> {}
//定義抽象的類實現Map接口
public abstract class AbstractMap<K,V> implements Map<K,V>{}
//AbstractHashedMap繼承抽象map
public class AbstractHashedMap extends AbstractMap implements IterableMap{}
2可以通過entrySet獲取當前數組的所有條目。
public abstract Set<Entry<K,V>> entrySet();
3 HashMap定義
3.1初始容量:默認map容量,默認爲16
3.2加載因子:爲設定臨界值的維度,默認爲0.75。
3.3容量臨界值:在put後會如果當前的大小大於舊的數組大小,需要重新計劃臨界值。
3.4最大容量:1.8jdk規定最大爲2的30次方。
protected static final int DEFAULT_CAPACITY = 16;//初始容量
protected static final int DEFAULT_THRESHOLD = 12;//容量臨界值=初始容量*默認加載因子
protected static final float DEFAULT_LOAD_FACTOR = 0.75F;//默認加載因子0.75
protected static final int MAXIMUM_CAPACITY = 1073741824;//最大容量爲2的30次方
protected static final Object NULL = new Object();
protected transient float loadFactor;
protected transient int size;//已經存在的個數
protected transient AbstractHashedMap.HashEntry[] data;//數組
protected transient int threshold;
protected transient int modCount;//已從結構上修改 此列表的次數
4 HashMap的構造函數
JDK源碼中有3個構造函數,最後都調用AbstractHashedMap(int initialCapacity,float loadFactor)方法。
/**
* initialCapacity : 初始化容量,默認16
* loadFactor : 初始化加載因子,默認0.75
**/
protected AbstractHashedMap(int initialCapacity, float loadFactor) {
if(initialCapacity < 1) {
throw new IllegalArgumentException("Initial capacity must be greater than 0");
} else if(loadFactor > 0.0F && !Float.isNaN(loadFactor)) {
this.loadFactor = loadFactor;
// 判斷初始化容量是否大於hashMap定義大小(2的30次方),如果大於設置初始大小爲2的30次方,如果小於,設置大小爲2(1<<=1)
initialCapacity = this.calculateNewCapacity(initialCapacity);
//初始化臨界值:(int)((float)initialCapacity* loadFactor)
this.threshold = this.calculateThreshold(initialCapacity, loadFactor);
//初始化數組大小,通過initialCapacity
this.data = new AbstractHashedMap.HashEntry[initialCapacity];
this.init();
} else {
throw new IllegalArgumentException("Load factor must be greater than 0");
}
}
面試問題:HashMap的默認大小是多大?
HashMap的默認大小爲16
/**
* 如果傳遞參數的map的大小大於16,那麼會創建size*2的容量。
**/
protected AbstractHashedMap(Map map) {
this(Math.max(2 * map.size(), 16), 0.75F);
this.putAll(map);
}
面試問題:爲什麼每次創建都是爲2的冪次?
判斷初始化容量是否大於hashMap定義大小(2的30次方),如果大於設置初始大小爲2的30次方,如果小於,設置大小爲2(1 << = 1);通過左移位賦值運算符進行計算<< 1
protected int calculateNewCapacity(int proposedCapacity) {
int newCapacity = 1;
if(proposedCapacity > 1073741824) {
newCapacity = 1073741824;
} else {
while(true) {
if(newCapacity >= proposedCapacity) {
if(newCapacity > 1073741824) {
newCapacity = 1073741824;
}
break;
}
newCapacity <<= 1;//左移位賦值運算符
}
}
return newCapacity;
}
5放(Object key,Object value)方法
/**
* 邏輯步驟
* 1 判斷key是否爲空,支持NULL關鍵字
* 2 取key的hashCode值
* 3 通過key的hashCode值與當前map的長度進行&與運算,得到數組下標
(&運算:將兩端轉化爲二進制,進行對應位比較,如果相等返回1,不想等返回0)
* 4 循環當前map是否存在key,如果存在比較==/equals是否相等,
* 4.1 如果相等更新key的值,返回舊的值
* 5 不存在則創建HashEntry對象,
* 6 將HashEntry存放到數組中指定下標位置
**/
public Object put(Object key, Object value) {
// 判斷key是否爲空,如果爲空,返回NULL
key = this.convertKey(key);
//得到key的hash()值
int hashCode = this.hash(key);
//得到數組下標:hashCode & this.data.length- 1;
int index = this.hashIndex(hashCode, this.data.length);
// 循環當前數組,判斷是否選在當前key
for(AbstractHashedMap.HashEntry entry = this.data[index]; entry != null; entry = entry.next) {
// 如果存在key進行比較,如果完全對象相等,值相等則替換key值,返回舊的值
if(entry.hashCode == hashCode && this.isEqualKey(key, entry.key)) {
Object oldValue = entry.getValue();
this.updateEntry(entry, value);
return oldValue;
}
}
this.addMapping(index, hashCode, key, value);
return null;
}
protected void addMapping(int hashIndex, int hashCode, Object key, Object value) {
++this.modCount;
//創建HashEntry對象,
AbstractHashedMap.HashEntry entry = this.createEntry(this.data[hashIndex], hashCode, key, value);
this.addEntry(entry, hashIndex);
//將enrty存放到指定數組下標位置
-->this.data[hashIndex] = entry;
//設置房前的map大小+1
++this.size;
//檢查map容量
this.checkCapacity();
}
/**
* 檢查容量
* 邏輯步驟:
* 1 判斷當前map大小大於等於容量界定值(默認12) && map大小*2後小於最大容量(2的32次方)
* 2 重新進行容量的初始化
* 3 實例化新的數組,並將old數組集合循環賦值給新的數組集合
* 4 根據新的容量*加載因子計算臨界值
**/
protected void checkCapacity() {
if(this.size >= this.threshold) {
int newCapacity = this.data.length * 2;
if(newCapacity <= 1073741824) {
this.ensureCapacity(newCapacity);
}
}
}
protected void ensureCapacity(int newCapacity) {
int oldCapacity = this.data.length;
if(newCapacity > oldCapacity) {
if(this.size == 0) {
this.threshold = this.calculateThreshold(newCapacity, this.loadFactor);
this.data = new AbstractHashedMap.HashEntry[newCapacity];
} else {
AbstractHashedMap.HashEntry[] oldEntries = this.data;
AbstractHashedMap.HashEntry[] newEntries = new AbstractHashedMap.HashEntry[newCapacity];
++this.modCount;
for(int i = oldCapacity - 1; i >= 0; --i) {
AbstractHashedMap.HashEntry entry = oldEntries[i];
if(entry != null) {
oldEntries[i] = null;
AbstractHashedMap.HashEntry next;
do {
next = entry.next;
int index = this.hashIndex(entry.hashCode, newCapacity);
entry.next = newEntries[index];
newEntries[index] = entry;
entry = next;
} while(next != null);
}
}
//計算新的加載因子並複製
this.threshold = this.calculateThreshold(newCapacity, this.loadFactor);
//-->(int)((float)newCapacity * loadFactor);
this.data = newEntries;
}
}
}
6 get(String key)
/*** 1 檢查key是否爲空
* 2 獲取key的HashCode值
* 3 循環遍歷得到HashEntry,返回entry的值
**/
public Object get(Object key) {
key = this.convertKey(key);
int hashCode = this.hash(key);
for(AbstractHashedMap.HashEntry entry = this.data[this.hashIndex(hashCode, this.data.length)]; entry != null; entry = entry.next) {
if(entry.hashCode == hashCode && this.isEqualKey(key, entry.key)) {
return entry.getValue();
}
}
return null;
}