Android面試之HashMap的實現原理

來源:互聯網
上載者:User

標籤:通過   一句話   dentry   entry   initial   遍曆   alc   androi   post   

1、HashMap與HashTable的區別
  • HashMap允許key和value為null;

  • HashMap是非同步的,線程不安全,也可以通過Collections.synchronizedMap()方法來得到一個同步的HashMap

  • HashMap存取速度更快,效率高

  • HashMap去掉了HashTable中的contains方法,加上了containsValue和containsKey方法

2、HashMap的實現原理

一句話理解HashMap:HashMap就是Hash表的Map實現。 Hash表就是Hash數組,Map實現是指實現了Map介面。

  1. HashMap的資料結構

    HashMap的底層是基於數組和鏈表實現的,儲存速度快的原因是因為它是通過計算散列碼來決定儲存的位置。HashMap中主要是通過key的hashCode來計算hash值的,只要hashCode相同,計算出來的hash值就一樣。如果儲存的對象對多了,就有可能不同的對象所算出來的hash值是相同的,這就出現了所謂的hash衝突。解決hash衝突的方法有很多,HashMap底層是通過鏈表來解決hash衝突的。

HashMap中Entry類源碼

static class Entry<K,V> implements Map.Entry<K,V> {

final K key;

V value;

Entry<K,V> next;

final int hash;

Entry(int h, K k, V v, Entry<K,V> n) {

value = v;

next = n;

key = k;

hash = h;

}

public final K getKey() {

return key;

}

public final V getValue() {

return value;

}

public final V setValue(V newValue) {

V oldValue = value;

value = newValue;

return oldValue;

}

public final boolean equals(Object o) {

if (!(o instanceof Map.Entry))

return false;

Map.Entry e = (Map.Entry)o;

Object k1 = getKey();

Object k2 = e.getKey();

if (k1 == k2 || (k1 != null && k1.equals(k2))) {

Object v1 = getValue();

Object v2 = e.getValue();

if (v1 == v2 || (v1 != null && v1.equals(v2)))

return true;

}

return false;

}

public final int hashCode() {

return (key==null ? 0 : key.hashCode()) ^

(value==null ? 0 : value.hashCode());

}

public final String toString() {

return getKey() + "=" + getValue();

}

void recordAccess(HashMap<K,V> m) {

}

void recordRemoval(HashMap<K,V> m) {

}

}

HashMap其實就是一個Entry數組,Entry對象中包含了鍵和值,其中next也是一個Entry對象,它就是用來處理hash衝突的,形成一個鏈表。

2. HashMap的幾個變數

transient Entry[] table;//儲存Entry的數組

transient int size;//存放entry的個數

int threshold;//下限,臨界值,數組長度超過這個值會擴容,threshold = loadFactor*容量;

final float loadFactory;//載入因子,載入因子越大表示當前數組填滿的元素越多,空間利用率高,不方便查詢;載入因子越小,表示填滿元素越少,空間利用率低,方便速度快。預設值是0.75;

transient int modCount;//被修改的次數

3. HashMap的構造方法

public HashMap(int initialCapacity, float loadFactor) {

if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " +

initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY)

initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " +

loadFactor); // Find a power of 2 >= initialCapacity

int capacity = 1;

while (capacity < initialCapacity)

capacity <<= 1; this.loadFactor = loadFactor;

threshold = (int)(capacity * loadFactor);

table = new Entry[capacity];

init();

}

允許我們自己指定初始容量和載入因子

public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR);

}

只指定初始容量,載入因子使用預設的0.75;

public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR;

threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);

table = new Entry[DEFAULT_INITIAL_CAPACITY];

init();

}

初始容量和載入因子全部使用預設的值,預設初始的載入容量是16,載入因子是0.75;

4. 儲存資料put方法

public V put(K key, V value) {

if (key == null)

return putForNullKey(value);

int hash = hash(key.hashCode());

int i = indexFor(hash, table.length);

for (Entry<K,V> e = table[i]; e != null; e = e.next) {

Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

V oldValue = e.value;

e.value = value;

e.recordAccess(this); return oldValue;

}

}

modCount++;

addEntry(hash, key, value, i); return null;

}

可以看出,HashMap存放資料的時候,會先拿到key的hashCode值,對其進行hash運算,得到具體的hash值,然後根據hash值尋找存放的位置,找到存放的位置後,然後遍曆table數組找到對應位置的entry,如果當前的key已經存在,且對比entry的hash值和key相同的話,那麼就更新value值;否則就將key和value值加到數組中;

這裡需要注意:判斷是否2個Entry相同,需要從2方面判斷:1、2個Entry的key必須相同(k = e.key||key.equals(k));2、2個Entry的hashCode,也就是hash值必須相同(這裡說的hash是經過hash(hashCode)換算後的值);上述2個條件都滿足的話,才可以認定當前Entry已經存在,新的Entry才會替換舊的Entry。

下面給出put過程中,涉及到的2個方法

- 根據hashCode求hash碼的實現

static int hash(int h) {

h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4);

}

- 根據hash碼求儲存在數組中的索引

static int indexFor(int h, int length) { //根據hash值和數組長度算出索引值2 return h & (length-1); //這裡不能隨便算取,用hash&(length-1)是有原因的,這樣可以確保算出來的索引是在數組大小範圍內,不會超出3 }

這裡有些同學會好奇,為什麼索引值是h & (length-1)? 其實,這裡面的故事還是挺多的。 一般對雜湊表求散列我們會使用hash值對length模數,HashTable就是這樣實現的,這種方法可以保證元素在雜湊表中散列均勻,但模數會用到除法運算,效率非常低,HashMap是對HashTable進行改進後的實現,為了提高效率,使用h&(length-1)取代除法運算,在保證散列均勻的基礎上還保持了效率的提升。

5. resize方法

用於重新擴大縮小數組的大小

void resize(int newCapacity) {

Entry[] oldTable = table;

int oldCapacity = oldTable.length;

if (oldCapacity == MAXIMUM_CAPACITY) {

threshold = Integer.MAX_VALUE;

return;

}

Entry[] newTable = new Entry[newCapacity];

transfer(newTable);

table = newTable;

threshold = (int)(newCapacity * loadFactor);

}

其實就是建立一個大的數組,把原來的資料重新添加進去。什麼時候才會觸發resize方法呢?噹噹前元素的數量達到數組總size的loadFactor(預設0.75)時候,會調用resize方法,將數組擴大為原來大小的2倍;

6. HashMap的get方法

public V get(Object key) {

if (key == null)

return getForNullKey();

int hash = hash(key.hashCode());

for (Entry<K,V> e = table[indexFor(hash, table.length)];

e != null;

e = e.next) {

Object k;

if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

return e.value;

}

return null;

}

可以看出,首先拿到key的hashcode,求出hash碼,根據hash碼找到索引的位置,然後去數組中擷取對應索引的元素,如果key的hash相同,key相同的話,那麼這就是我們要找的entry,把entry的值返回出去就Ok了。

如果大家覺得好,大家轉載的同時,也點點文章最下面“AndroidDeveloper”的訂閱按鈕,關注“AndroidDeveloper”

Android面試之HashMap的實現原理

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.