HashMap小解

第一次實(shí)習(xí)面試的時(shí)候,小房間里面試官拿著一本本子,上來就告訴我自己是做Java的,然后問了我很多Java基礎(chǔ),當(dāng)從HashMap如何使用到怎么實(shí)現(xiàn)的時(shí)候,我就兩眼懵逼了,當(dāng)問到安卓的Context是干什么的時(shí)候,我的內(nèi)心直接被擊中了。雖然寫了好幾個(gè)月的程序,卻只停留在會用的基礎(chǔ)上,而沒去知其所以然,太臉紅了。這次面試打開了我對程序語言執(zhí)著研究的大門,還是菜鳥的我感覺到能問出這種問題,還如此謙虛的人真是厲害,懷抱著大神帶我飛的心情,幾乎熱淚盈眶的我欣然帶著這份offer,準(zhǔn)備跟著大神大干一場。結(jié)果當(dāng)我入職的事后才知道,大神在我來的前幾天就去支付寶了...言歸正傳,我就整理八經(jīng)研究一下這個(gè)HashMap。

HashMap

HashMap是程序編寫過程中,再平常不過的一個(gè)集合類,通常通過他的put(key,value)get(key)方法來存取數(shù)據(jù),數(shù)據(jù)存取的類型非常多樣,而且允許keyvaluenull。直接上源碼:
HashMap

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, 
Cloneable, Serializable { ... }

AbstractMap

public abstract class AbstractMap<K,V> implements Map<K,V> { ... }

HashMap繼承于AbstractMap,Map定義了鍵值對的映射規(guī)則。AbstractMap內(nèi)部也也實(shí)現(xiàn)了Map接口,
這是為什么嘞?難道是為了強(qiáng)調(diào)嗎,或者是反射getInterfaces的時(shí)候能夠直接獲取到Map,這里不是和明白,后續(xù)再補(bǔ)吧。

構(gòu)造方法

HashMap的構(gòu)造方法四個(gè)
Ps: 全局變量中已經(jīng)指定默認(rèn)的加載因子0.75,初始容量16

public HashMap(int initialCapacity, float loadFactor)
<!--構(gòu)造一個(gè)帶指定初始容量和默認(rèn)加載因子(0.75)的空HashMap-->
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

<!--構(gòu)造一個(gè)具有默認(rèn)初始容量(16)和默認(rèn)加載因子(0.75)的空HashMap-->
public HashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

public HashMap(Map<? extends K, ? extends V> m){
    this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
        DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
}

殊途同歸,最終都指向了第一個(gè)構(gòu)造方法。嗯,我們來看一下。

public HashMap(int initialCapacity, float loadFactor)

這里有初始容量,加載因子兩個(gè)參數(shù),影響HashMap性能。

初始容量 是哈希表在創(chuàng)建時(shí)的容量。

加載因子 是哈希表在其容量自動增加之前可以達(dá)到多滿的一種尺度。當(dāng)哈希表中的條目數(shù)超出了加載因子與當(dāng)前容量的乘積時(shí),通過調(diào)用rehash 方法將容量翻倍。

以下內(nèi)容來自百度對于他們的理解:
比如說向水桶中裝水,此時(shí)HashMap就是一個(gè)桶, 這個(gè)桶的容量就是加載容量,而加載因子就是你要控制向這個(gè)桶中倒的水不超過水桶容量的比例,比如加載因子是0.75 ,那么在裝水的時(shí)候這個(gè)桶最多能裝到3/4 處,超過這個(gè)比例時(shí),桶會自動擴(kuò)容。因此,這個(gè)桶
最多能裝水 = 桶的容量 x 加載因子。
如果桶的容量是40,加載因子是0.75
那么你的桶最多能裝40 x 0.75 = 30的水,如果你裝了30的水還想繼續(xù)裝水,那么就該用大一點(diǎn)的桶,調(diào)用rehash就是負(fù)責(zé)增加桶的容量的方法。

數(shù)據(jù)結(jié)構(gòu)

Java中最常用的兩種結(jié)構(gòu)是數(shù)組和模擬指針(引用),幾乎所有的數(shù)據(jù)結(jié)構(gòu)都可以利用這兩種來組合實(shí)現(xiàn),HashMap也是如此。實(shí)際上HashMap是一個(gè)“鏈表散列”,網(wǎng)上扒取了一張結(jié)構(gòu)圖:

hashmap結(jié)構(gòu)圖示例

HashMap底層還是數(shù)組,只是數(shù)組的每一項(xiàng)都是一條鏈,initialCapacity 代表數(shù)組長度。

來吧,看看構(gòu)造函數(shù)的內(nèi)容:

public HashMap(int initialCapacity, float loadFactor) {
<!--   初始容量必須大于或者等于0  -->
   if (initialCapacity < 0)
       throw new IllegalArgumentException("Illegal initial capacity: " +
               initialCapacity);
   <!--  初始容量不能 > 最大容量值,HashMap的最大容量值為2^30  -->
   if (initialCapacity > MAXIMUM_CAPACITY) {
       initialCapacity = MAXIMUM_CAPACITY;
   } else if (initialCapacity < DEFAULT_INITIAL_CAPACITY) {
       initialCapacity = DEFAULT_INITIAL_CAPACITY;
   }
        <!--   加載因子不能小于0   -->
   if (loadFactor <= 0 || Float.isNaN(loadFactor))
       throw new IllegalArgumentException("Illegal load factor: " +
               loadFactor);
   threshold = initialCapacity;
   <!--  這個(gè)方法留給子類重寫,里面什么都沒有  -->
   init();    
}

存儲數(shù)據(jù)

數(shù)據(jù)存儲用的是put(k,v)的方式

public V put(K key, V value) {
        // 如果表是空的 就新建一個(gè)HashTable
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }

        // key可以是null,調(diào)用putForNullKey方法,保存null與table第一個(gè)位置中
        if (key == null)
            return putForNullKey(value);
        // 獲取key的哈希
        int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);

        // 計(jì)算key hash 值在 table 數(shù)組中的位置
        int i = indexFor(hash, table.length);

        // 迭代找出key的位置,為了得到 value
        for (HashMapEntry<K, V> e = table[i]; e != null; e = e.next) {
            Object k;
            // 如果key重復(fù)了 返回老的value (所以put的結(jié)果是個(gè)object)
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        // 添加一次修改
        modCount++;
        // 將key、value添加至i位置處 替換
        addEntry(hash, key, value, i);
        return null;
    }

計(jì)算哈希值的方法

static int hash(int h) {
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

我們知道對于HashMap的table而言,數(shù)據(jù)分布需要均勻(最好每項(xiàng)都只有一個(gè)元素,這樣就可以直接找到),不能太緊也不能太松,太緊會導(dǎo)致查詢速度慢,太松則浪費(fèi)空間。計(jì)算hash值后,怎么才能保證table元素分布均與呢?我們會想到取模,但是由于取模的消耗較大,HashMap是這樣處理的:調(diào)用indexFor方法。

static int indexFor(int h, int length) {
<!--assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";-->
        return h & (length - 1);
 }

HashMap的底層數(shù)組長度總是2的n次方,在構(gòu)造函數(shù)中存在:capacity <<= 1;這樣做總是能夠保證HashMap的底層數(shù)組長度為2的n次方。當(dāng)length為2的n次方時(shí),h&(length - 1)就相當(dāng)于對length取模,而且速度比直接取模快得多,這是HashMap在速度上的一個(gè)優(yōu)化。

取數(shù)據(jù)

數(shù)據(jù)獲取用的是get(key)的形式。

public V get(Object key) {
    <!--key為空的情況下,內(nèi)容的獲取-->
    if (key == null)
        return getForNullKey();
    Entry<K, V> entry = getEntry(key);
    return null == entry ? null : entry.getValue();
}
<!--獲取方法-->
private V getForNullKey() {
  if (size == 0) {
   return null;
 }
 for (HashMapEntry<K, V> e = table[0]; e != null; e = e.next) {
   if (e.key == null)
    return e.value;
}
return null;
}

還有個(gè)比較重要的方法

final Entry<K, V> getEntry (Object key) {
  if (size == 0) {
    return null;
  }
  int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
  for (HashMapEntry<K, V> e = table[indexFor(hash, table.length)]; e != null;
    e = e.next) {
    Object k;
  if (e.hash == hash && ((k = e.key) == key || (key != null &&key.equals(k)))) 
    return e;
}
return null;
}

Entry是Map接口內(nèi)的一個(gè)接口,他的作用就是包裝一個(gè)map的節(jié)點(diǎn),這個(gè)節(jié)封裝了key,value,以及別的值(比如hashmap中的哈希碼和next指針),方便對Map的操作。

總結(jié)

由此看出,數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)不好,看這個(gè)真費(fèi)勁。明白是明白,但是真叫你一點(diǎn)點(diǎn)寫出來,是想撲街的。

HashMap基于hashing原理,我們通過put()和get()方法儲存和獲取對象。當(dāng)我們將鍵值對傳遞給put()方法時(shí),它調(diào)用鍵對象的hashCode()方法來計(jì)算hashcode,然后找到bucket位置來儲存值對象。當(dāng)獲取對象時(shí),通過鍵對象的equals()方法找到正確的鍵值對,然后返回值對象。HashMap使用鏈表來解決碰撞問題,當(dāng)發(fā)生碰撞了,對象將會儲存在鏈表的下一個(gè)節(jié)點(diǎn)中。 HashMap在每個(gè)鏈表節(jié)點(diǎn)中儲存鍵值對對象。
當(dāng)兩個(gè)不同的鍵對象的hashcode相同時(shí)會發(fā)生什么? 它們會儲存在同一個(gè)bucket位置的鏈表中。鍵對象的equals()方法用來找到鍵值對。
因?yàn)镠ashMap的好處非常多,我曾經(jīng)在電子商務(wù)的應(yīng)用中使用HashMap作為緩存。因?yàn)榻鹑陬I(lǐng)域非常多的運(yùn)用Java,也出于性能的考慮,我們會經(jīng)常用到HashMap和ConcurrentHashMap。

尊重原創(chuàng),借鑒:http://www.cnblogs.com/chenssy/p/3521565.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,995評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容