日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

详解HashMap的内部工作原理

發(fā)布時(shí)間:2025/3/20 编程问答 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 详解HashMap的内部工作原理 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文將用一個(gè)簡(jiǎn)單的例子來(lái)解釋下HashMap內(nèi)部的工作原理。首先我們從一個(gè)例子開(kāi)始,而不僅僅是從理論上,這樣,有助于更好地理解,然后,我們來(lái)看下get和put到底是怎樣工作的。

我們來(lái)看個(gè)非常簡(jiǎn)單的例子。有一個(gè)”國(guó)家”(Country)類(lèi),我們將要用Country對(duì)象作為key,它的首都的名字(String類(lèi)型)作為value。下面的例子有助于我們理解key-value對(duì)在HashMap中是如何存儲(chǔ)的。

1. Country.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package org.arpit.javapostsforlearning; public class Country { ? ?String name; ?long population; ? ?public Country(String name, long population) { ??super(); ??this.name = name; ??this.population = population; ?} ?public String getName() { ??return name; ?} ?public void setName(String name) { ??this.name = name; ?} ?public long getPopulation() { ??return population; ?} ?public void setPopulation(long population) { ??this.population = population; ?} ? ?// If length of name in country object is even then return 31(any random number) and if odd then return 95(any random number). ?// This is not a good practice to generate hashcode as below method but I am doing so to give better and easy understanding of hashmap. ?@Override ?public int hashCode() { ??if(this.name.length()%2==0) ???return 31; ??else ???return 95; ?} ?@Override ?public boolean equals(Object obj) { ? ??Country other = (Country) obj; ???if (name.equalsIgnoreCase((other.name))) ???return true; ??return false; ?} ? }

如果想了解更多關(guān)于Object對(duì)象的hashcode和equals方法的東西,可以參考:java中的hashcode()和equals()方法

2. HashMapStructure.java(main class)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import java.util.HashMap; import java.util.Iterator; ??? public class HashMapStructure { ??? ????/** ?????* @author Arpit Mandliya ?????*/ ????public static void main(String[] args) { ??????????? ????????Country india=new Country("India",1000); ????????Country japan=new Country("Japan",10000); ??????????? ????????Country france=new Country("France",2000); ????????Country russia=new Country("Russia",20000); ??????????? ????????HashMap<country,string> countryCapitalMap=new HashMap<country,string>(); ????????countryCapitalMap.put(india,"Delhi"); ????????countryCapitalMap.put(japan,"Tokyo"); ????????countryCapitalMap.put(france,"Paris"); ????????countryCapitalMap.put(russia,"Moscow"); ??????????? ????????Iterator<country> countryCapitalIter=countryCapitalMap.keySet().iterator();//put debug point at this line ????????while(countryCapitalIter.hasNext()) ????????{ ????????????Country countryObj=countryCapitalIter.next(); ????????????String capital=countryCapitalMap.get(countryObj); ????????????System.out.println(countryObj.getName()+"----"+capital); ????????????} ????????} ??? ??? }

現(xiàn)在,在第23行設(shè)置一個(gè)斷點(diǎn),在項(xiàng)目上右擊->調(diào)試運(yùn)行(debug as)->java應(yīng)用(java application)。程序會(huì)停在23行,然后在countryCapitalMap上右擊,選擇“查看”(watch)。將會(huì)看到如下的結(jié)構(gòu):

從上圖可以觀察到以下幾點(diǎn):

  • 有一個(gè)叫做table大小是16的Entry數(shù)組。

  • 這個(gè)table數(shù)組存儲(chǔ)了Entry類(lèi)的對(duì)象。HashMap類(lèi)有一個(gè)叫做Entry的內(nèi)部類(lèi)。這個(gè)Entry類(lèi)包含了key-value作為實(shí)例變量。我們來(lái)看下Entry類(lèi)的結(jié)構(gòu)。Entry類(lèi)的結(jié)構(gòu):

  • 1 2 3 4 5 6 7 8 static class Entry implements Map.Entry { ????????final K key; ????????V value; ????????Entry next; ????????final int hash; ????????...//More code goes here }?? `
  • 每當(dāng)往hashmap里面存放key-value對(duì)的時(shí)候,都會(huì)為它們實(shí)例化一個(gè)Entry對(duì)象,這個(gè)Entry對(duì)象就會(huì)存儲(chǔ)在前面提到的 Entry數(shù)組table中。現(xiàn)在你一定很想知道,上面創(chuàng)建的Entry對(duì)象將會(huì)存放在具體哪個(gè)位置(在table中的精確位置)。答案就是,根據(jù)key 的hashcode()方法計(jì)算出來(lái)的hash值(來(lái)決定)。hash值用來(lái)計(jì)算key在Entry數(shù)組的索引。

  • 現(xiàn)在,如果你看下上圖中數(shù)組的索引10,它有一個(gè)叫做HashMap$Entry的Entry對(duì)象。

  • 我們往hashmap放了4個(gè)key-value對(duì),但是看上去好像只有2個(gè)元素!!!這是因?yàn)?#xff0c;如果兩個(gè)元素有相同的hashcode,它們會(huì)被放在同一個(gè)索引上。問(wèn)題出現(xiàn)了,該怎么放呢?原來(lái)它是以鏈表(LinkedList)的形式來(lái)存儲(chǔ)的(邏輯上)。

  • 上面的country對(duì)象的key-value的hash值是如何計(jì)算出來(lái)的。

    Japan的Hash值是95,它的長(zhǎng)度是奇數(shù)。India的Hash值是95,它的長(zhǎng)度是奇數(shù)。Russia的Hash值是31,它的長(zhǎng)度是偶數(shù)。France的Hash值是31,它的長(zhǎng)度是偶數(shù)。

    ?

    下圖會(huì)清晰的從概念上解釋下鏈表。

    所以,現(xiàn)在假如你已經(jīng)很好地了解了hashmap的結(jié)構(gòu),讓我們看下put和get方法。

    Put :

    讓我們看下put方法的實(shí)現(xiàn):

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 /** ??* Associates the specified value with the specified key in this map. If the ??* map previously contained a mapping for the key, the old value is ??* replaced. ??* ??* @param key ??*??????????? key with which the specified value is to be associated ??* @param value ??*??????????? value to be associated with the specified key ??* @return the previous value associated with <tt>key</tt>, or <tt>null</tt> ??*???????? if there was no mapping for <tt>key</tt>. (A <tt>null</tt> return ??*???????? can also indicate that the map previously associated ??*???????? <tt>null</tt> with <tt>key</tt>.) ??*/ ?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; ?}

    現(xiàn)在我們一步一步來(lái)看下上面的代碼。

  • 對(duì)key做null檢查。如果key是null,會(huì)被存儲(chǔ)到table[0],因?yàn)閚ull的hash值總是0。

  • key的hashcode()方法會(huì)被調(diào)用,然后計(jì)算hash值。hash值用來(lái)找到存儲(chǔ)Entry對(duì)象的數(shù)組的索引。有時(shí)候hash函數(shù)可能寫(xiě)的 很不好,所以JDK的設(shè)計(jì)者添加了另一個(gè)叫做hash()的方法,它接收剛才計(jì)算的hash值作為參數(shù)。如果你想了解更多關(guān)于hash()函數(shù)的東西,可 以參考:hashmap中的hash和indexFor方法

  • indexFor(hash,table.length)用來(lái)計(jì)算在table數(shù)組中存儲(chǔ)Entry對(duì)象的精確的索引。

  • 在我們的例子中已經(jīng)看到,如果兩個(gè)key有相同的hash值(也叫沖突),他們會(huì)以鏈表的形式來(lái)存儲(chǔ)。所以,這里我們就迭代鏈表。

    • 如果在剛才計(jì)算出來(lái)的索引位置沒(méi)有元素,直接把Entry對(duì)象放在那個(gè)索引上。
    • 如果索引上有元素,然后會(huì)進(jìn)行迭代,一直到Entry->next是null。當(dāng)前的Entry對(duì)象變成鏈表的下一個(gè)節(jié)點(diǎn)。
    • 如果我們?cè)俅畏湃胪瑯拥膋ey會(huì)怎樣呢?邏輯上,它應(yīng)該替換老的value。事實(shí)上,它確實(shí)是這么做的。在迭代的過(guò)程中,會(huì)調(diào)用equals() 方法來(lái)檢查key的相等性(key.equals(k)),如果這個(gè)方法返回true,它就會(huì)用當(dāng)前Entry的value來(lái)替換之前的value。

    Get:

    現(xiàn)在我們來(lái)看下get方法的實(shí)現(xiàn):

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 /** ??* Returns the value to which the specified key is mapped, or {@code null} ??* if this map contains no mapping for the key. ??* ??* <p> ??* More formally, if this map contains a mapping from a key {@code k} to a ??* value {@code v} such that {@code (key==null ? k==null : ??* key.equals(k))}, then this method returns {@code v}; otherwise it returns ??* {@code null}. (There can be at most one such mapping.) ??* ??* </p><p> ??* A return value of {@code null} does not <i>necessarily</i> indicate that ??* the map contains no mapping for the key; it's also possible that the map ??* explicitly maps the key to {@code null}. The {@link #containsKey ??* containsKey} operation may be used to distinguish these two cases. ??* ??* @see #put(Object, Object) ??*/ ?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; ?}

    當(dāng)你理解了hashmap的put的工作原理,理解get的工作原理就非常簡(jiǎn)單了。當(dāng)你傳遞一個(gè)key從hashmap總獲取value的時(shí)候:

  • 對(duì)key進(jìn)行null檢查。如果key是null,table[0]這個(gè)位置的元素將被返回。

  • key的hashcode()方法被調(diào)用,然后計(jì)算hash值。

  • indexFor(hash,table.length)用來(lái)計(jì)算要獲取的Entry對(duì)象在table數(shù)組中的精確的位置,使用剛才計(jì)算的hash值。

  • 在獲取了table數(shù)組的索引之后,會(huì)迭代鏈表,調(diào)用equals()方法檢查key的相等性,如果equals()方法返回true,get方法返回Entry對(duì)象的value,否則,返回null。

  • 要牢記以下關(guān)鍵點(diǎn):

    • HashMap有一個(gè)叫做Entry的內(nèi)部類(lèi),它用來(lái)存儲(chǔ)key-value對(duì)。
    • 上面的Entry對(duì)象是存儲(chǔ)在一個(gè)叫做table的Entry數(shù)組中。
    • table的索引在邏輯上叫做“桶”(bucket),它存儲(chǔ)了鏈表的第一個(gè)元素。
    • key的hashcode()方法用來(lái)找到Entry對(duì)象所在的桶。
    • 如果兩個(gè)key有相同的hash值,他們會(huì)被放在table數(shù)組的同一個(gè)桶里面。
    • key的equals()方法用來(lái)確保key的唯一性。
    • value對(duì)象的equals()和hashcode()方法根本一點(diǎn)用也沒(méi)有。

    轉(zhuǎn)自:原文鏈接: javacodegeeks 翻譯: ImportNew.com - miracle1919
    ??????? 譯文鏈接: http://www.importnew.com/10620.html

    ??????? 作者:miracle1919

    轉(zhuǎn)載于:https://www.cnblogs.com/ziysong/p/4679353.html

    總結(jié)

    以上是生活随笔為你收集整理的详解HashMap的内部工作原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。