HashMap了解一下

前言

本文基于jdk1.8

HashMap

先來(lái)看一下Java官方描述

  • 基于哈希表的 Map 接口的實(shí)現(xiàn)。
  • 允許使用 null 值和 null 鍵。
  • 除了非同步和允許使用 null 之外苇本,HashMap 類與 Hashtable 大致相同喊括。
  • 不保證映射的順序,特別是它不保證該順序恒久不變(擴(kuò)容后元素在底層會(huì)重散列)虏杰。
  • 迭代所需的時(shí)間與 HashMap實(shí)例的“容量”(桶的數(shù)量)及鍵-值映射數(shù)成比例贱鼻。即初始容量設(shè)置得太高(或?qū)⒇?fù)載因子設(shè)置得太低)對(duì)遍歷的性能影響都很大喻奥。
    • 負(fù)載因子 = 總鍵值對(duì)數(shù) / 箱子個(gè)數(shù)(數(shù)組元素)
  • HashMap 的實(shí)例有兩個(gè)參數(shù)影響其性能:初始容量 和負(fù)載因子。當(dāng)元素個(gè)數(shù)超過(guò)當(dāng)前容量(桶數(shù))與負(fù)載因子的乘機(jī)時(shí)赖欣,哈希表會(huì)對(duì)其元素rehash屑彻,底層的桶數(shù)*2。
  • 默認(rèn)負(fù)載因子(0.75)顶吮,加載因子過(guò)高雖然減少了空間開銷社牲,但同時(shí)也增加了查詢成本。在設(shè)置初始容量時(shí)應(yīng)該考慮到Map中所要容納的元素?cái)?shù)量及其加載因子悴了,以便最大限度地減少rehash操作次數(shù)搏恤。如果初始容量大于最大條目數(shù)除以加載因子,則不會(huì)發(fā)生rehash操作湃交。
  • 如果從一開始就知道有多少元素要存到Map中熟空,最好一開始就指定好Map初始容量,盡量避免其rehash操作搞莺。性能會(huì)大大提高
  • 此實(shí)現(xiàn)不是同步的息罗。想要同步在一些列操作外手動(dòng)同步,或者使用Map m = Collections.synchronizedMap(new HashMap(...));方法來(lái)“包裝”該映射才沧。最好在創(chuàng)建時(shí)完成這一操作阱当,以防止對(duì)映射進(jìn)行意外的非同步訪問

HashMap類繼承圖

  • 實(shí)現(xiàn)了Serializable,Cloneable接口
  • 繼承了父類AbstractMap
image

HashMap屬性

靜態(tài)屬性:

  • DEFAULT_INITIAL_CAPACITY: 初始容量糜工,也就是默認(rèn)會(huì)創(chuàng)建 16 個(gè)箱子弊添,箱子的個(gè)數(shù)不能太多或太少。如果太少捌木,很容易觸發(fā)擴(kuò)容油坝,如果太多舔糖,遍歷哈希表會(huì)比較慢。
  • MAXIMUM_CAPACITY: 哈希表最大容量告唆,一般情況下只要內(nèi)存夠用巴帮,哈希表不會(huì)出現(xiàn)問題。
  • DEFAULT_LOAD_FACTOR: 默認(rèn)的負(fù)載因子瞬女。因此初始情況下窍帝,當(dāng)鍵值對(duì)的數(shù)量大于 16 * 0.75 = 12 時(shí),就會(huì)觸發(fā)擴(kuò)容诽偷。
  • TREEIFY_THRESHOLD: 上文說(shuō)過(guò)坤学,如果哈希函數(shù)不合理,即使擴(kuò)容也無(wú)法減少箱子中鏈表的長(zhǎng)度报慕,因此 Java 的處理方案是當(dāng)鏈表太長(zhǎng)時(shí)深浮,轉(zhuǎn)換成紅黑樹。這個(gè)值表示當(dāng)某個(gè)箱子中眠冈,鏈表長(zhǎng)度大于等于 8 時(shí)飞苇,有可能會(huì)轉(zhuǎn)化成樹。
  • UNTREEIFY_THRESHOLD: 在哈希表擴(kuò)容時(shí)蜗顽,如果發(fā)現(xiàn)鏈表長(zhǎng)度小于 6布卡,則會(huì)由樹重新退化為鏈表雇盖。
  • MIN_TREEIFY_CAPACITY: 在轉(zhuǎn)變成樹之前羽利,還會(huì)有一次判斷,只有鍵值對(duì)數(shù)量大于 64 才會(huì)發(fā)生轉(zhuǎn)換刊懈。這是為了避免在哈希表建立初期这弧,多個(gè)鍵值對(duì)恰好被放入了同一個(gè)鏈表中而導(dǎo)致不必要的轉(zhuǎn)化。

成員屬性:

image
  • table:HashMap的鏈表數(shù)組虚汛。它在自擴(kuò)容時(shí)總是2的次冪
  • entrySet: HashMap實(shí)例中的Entry的Set集合匾浪。
  • size: HashMap實(shí)例中映射的數(shù)量
  • modCount: 這個(gè)HashMap被結(jié)構(gòu)修改的次數(shù)
  • threshold: 元素的閾值
  • loadFactor: 哈希表的負(fù)載因子

HashMap簡(jiǎn)單總結(jié):

  • 存取無(wú)序
  • 鍵值允許為NULL
  • 非同步類
  • 底層是哈希表
  • 初始容量和裝載因子是決定整個(gè)類性能的關(guān)鍵點(diǎn)。

HashMap構(gòu)造函數(shù)

HashMap的構(gòu)造函數(shù)共4個(gè)卷哩。

image

HashMap(int initialCapacity, float loadFactor)

我們可以看到loadFactor參數(shù)Java官方還對(duì)其進(jìn)行了Float.NaN(0.0f/0.0f)的判斷蛋辈。可以說(shuō)考慮的非常周到了

同時(shí)在構(gòu)造函數(shù)的最后一行将谊,在為threshold賦值的時(shí)候冷溶,調(diào)用了tableSizeFor()函數(shù)來(lái)對(duì)輸入的初始容量進(jìn)行判斷,來(lái)保證該初始容量是2的整數(shù)次冪尊浓。

image

函數(shù)具體釋義可參考:HashMap源碼注解 之 靜態(tài)工具方法hash()逞频、tableSizeFor()(四)

  • 此時(shí)不經(jīng)要想為什么是將2的整數(shù)冪的數(shù)賦給threshold?

首先我們先來(lái)了解一下threshold這個(gè)屬性:

image

threshold這個(gè)成員變量是閾值栋齿,決定了是否要將散列表擴(kuò)容充重散列苗胀。它的值應(yīng)該是:(capacity * loadfactor)才對(duì)的襟诸。

  • 那我們就更奇怪了,那這里還給他賦值干什么基协?

其實(shí)這里僅僅是對(duì)該值的一個(gè)初始化歌亲,tableSizeFor函數(shù)賦予了該值一個(gè)最接近initialCapacity值的2的整數(shù)冪的值,當(dāng)調(diào)用putVul函數(shù)需要往元素容器中真正添加元素的時(shí)候澜驮,也就是真正創(chuàng)建哈希表的時(shí)候陷揪,該值才會(huì)在resize函數(shù)中根據(jù)一些條件判斷來(lái)重新定義賦值的。

HashMap(Map<? extends K, ? extends V> m)

image

這個(gè)構(gòu)造函數(shù)其實(shí)本質(zhì)上和剛才我們所看的給初始容量和負(fù)載因子的構(gòu)造函數(shù)一致杂穷。函數(shù)最終做的除了參數(shù)上可以看出來(lái)的悍缠,把一個(gè)Map實(shí)例中的元素都插入到HashMap實(shí)例中外,由于負(fù)載因子沒有顯示指定所以賦予了loadFactory默認(rèn)值亭畜。

并且把具體的Map迭代放在了調(diào)用的putMapEntries函數(shù)中。從該函數(shù)注釋中看出迎卤,該函數(shù)是服務(wù)于putAll函數(shù)和constructor構(gòu)造函數(shù)的拴鸵。

putMapEntries函數(shù)注釋中也可以看出,如果這個(gè)函數(shù)在初始化Map時(shí)被調(diào)用evict參數(shù)傳入的是false蜗搔。而如果這個(gè)函數(shù)是在afterNodeInsertion函數(shù)后調(diào)用的劲藐,那么則需要傳入的是true。

從putMapEntries函數(shù)的具體代碼實(shí)現(xiàn)上可以看出樟凄,其在Map第一次初始化的時(shí)候和我們剛才所看的構(gòu)造函數(shù)一致聘芜,都是在真正構(gòu)造HashMap前先為閾值賦予一個(gè)可靠地初值。然后才迭代入?yún)ap集合缝龄,將其元素都插入到HashMap中汰现。

其余的構(gòu)造函數(shù)都是大同小異。就不一一贅述了

總結(jié):

從構(gòu)造函數(shù)中我們可以看出叔壤,HashMap的構(gòu)造函數(shù)其實(shí)工作就只有一個(gè)瞎饲。賦值

  • 賦值負(fù)載因子
  • 賦值閾值

HashMap常見Api解析

put(K key, V value)

 /**
 * 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.
 *
 */
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

可以看出put函數(shù)具體實(shí)現(xiàn)其實(shí)是調(diào)用了putVal函數(shù)。并且從注釋中可以得知如果該映射存在炼绘,新值會(huì)替換掉舊值嗅战。

還可以看到的是傳入了5個(gè)參數(shù),我們來(lái)了解簡(jiǎn)單一下這5個(gè)參數(shù)俺亮。

 /**
 * Implements Map.put and related methods
 *
 * @param hash hash for key
 * @param key the key
 * @param value the value to put
 * @param onlyIfAbsent if true, don't change existing value
 * @param evict if false, the table is in creation mode.
 * @return previous value, or null if none
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {

注釋很直白解釋的很清楚驮捍,按序號(hào)來(lái)

  1. key的哈希值
  2. key
  3. value
  4. 如果映射存在不替換原值?
  5. 如果是false脚曾,代表當(dāng)前HashMap正處于創(chuàng)建階段东且。

我們大概知道了這5個(gè)參數(shù)是干嘛的了,就可以針對(duì)第一個(gè)參數(shù)具體的去了解一下key的哈希值是如何計(jì)算得了本讥。

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

這里就比較疑惑了苇倡,為什么不直接用key類型對(duì)應(yīng)的hashcode函數(shù)直接得到對(duì)應(yīng)的哈希碼呢富纸?從這段代碼可以看出來(lái)Java官方拿key的哈希碼和其哈希碼向右移位16位的結(jié)果進(jìn)行異或操作。我們知道右移高位補(bǔ)0旨椒,由于哈希碼返回的是一個(gè)4字節(jié)32位int類型的值晓褪。所以總共可以看成是高16位,低16位組成的综慎。

而這里對(duì)其哈希碼進(jìn)行右移16位之后高位空缺補(bǔ)0涣仿,原高位到了現(xiàn)低位。然后再拿原哈希碼與現(xiàn)哈希碼進(jìn)行異或操作示惊,而我們知道任何數(shù)跟0異或都是其本身好港。所以可以看出Java官方最終目的,是想讓哈希碼原低16位與其高16位進(jìn)行異或操作米罚。而原高16位卻不變钧汹,原來(lái)如此~

image

但為什么要這樣呢?

其實(shí)這個(gè)與HashMap中元素插入時(shí)對(duì)應(yīng)位置table數(shù)組下標(biāo)的計(jì)算有關(guān)录择。

image

核心代碼:

n = (tab = resize()).length;
i =(n-1) & hash

我們暫且不看resize函數(shù)拔莱,putVal函數(shù)在初次進(jìn)來(lái)的時(shí)候該resize函數(shù)做的其實(shí)就是哈希表初始化的工作。由于DEFAULT_INITIAL_CAPACITY變量的存在隘竭,我們暫且認(rèn)為該哈希表的大小length為16塘秦。即n=16

那么在計(jì)算元素插入位置的時(shí)候,由于底層是數(shù)組动看。所以真正存儲(chǔ)的位置就是[0-15]尊剔。所以n-1。

而因?yàn)榱饨裕瑃able的length屬性都是2的冪须误,因此i僅與hash值的低n位有關(guān)(此n非table.length,而是2的冪指數(shù))
假設(shè)table.length=2^4=16仇轻。

image

由上圖可以看到霹期,32位的hash值只有低4位參與了運(yùn)算。
僅有4位的異或很容易產(chǎn)生碰撞拯田。但是由于table.length的大小只能由插入數(shù)據(jù)的多少來(lái)改變历造,而hash值得高16位又由于與操作置0了,所以設(shè)計(jì)人員相出一個(gè)好辦法就是通過(guò)對(duì)key的hash值進(jìn)行高16位與低16位異或來(lái)將高位與低位整合船庇。這樣不但可以保留原來(lái)key哈希值的差異性(高位地位同時(shí)作用)吭产,同時(shí)來(lái)增加低16位2進(jìn)制數(shù)值在計(jì)算時(shí)的差異性進(jìn)而減少這種哈希沖突的情況。

參考:HashMap源碼注解 之 靜態(tài)工具方法hash()鸭轮、tableSizeFor()(四)

putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)

接下來(lái)便可以繼續(xù)分析真正的put函數(shù)了臣淤。

總結(jié)下來(lái)就是:

  • 散列表初次put數(shù)組任為NULL時(shí),調(diào)用resize函數(shù)(單獨(dú)拉出來(lái)說(shuō))來(lái)初始化散列表
  • 當(dāng)根據(jù)元素的哈希值可以確認(rèn)分配到散列表的下標(biāo)時(shí)窃爷,如果此處沒有元素邑蒋,那么就直接插入
    • 當(dāng)該元素就是和我一樣的元素姓蜂。即此處元素的hash值和key值都與要插入的元素相等時(shí)。通過(guò)變量e指向該元素医吊。
    • 當(dāng)該元素已經(jīng)不是一個(gè)鏈表結(jié)構(gòu)而是一個(gè)紅黑樹結(jié)構(gòu)的時(shí)候(元素肯定過(guò)6)钱慢,將該元素插入到紅黑樹中,并用e指向該元素
    • 否則該處就是一個(gè)鏈表卿堂,鏈表頭結(jié)點(diǎn)不是要插入的元素束莫,但不能排除其他節(jié)點(diǎn)不是。所以然后遍歷這個(gè)數(shù)組草描,當(dāng)該鏈表中遍歷到的節(jié)點(diǎn)的下一節(jié)點(diǎn)為空時(shí)將該元素插入到鏈表尾览绿,循環(huán)跳出。如果不為空的話穗慕,對(duì)下一節(jié)點(diǎn)進(jìn)行判斷看是否是待插入節(jié)點(diǎn)饿敲。如果是跳出
  • 程序進(jìn)行到綠框時(shí),此時(shí)程序只有兩種情況逛绵。元素最后找到空出插入了怀各,但是e無(wú)法指向∈畲啵或者說(shuō)元素找到和自己一樣的元素了渠啤,并且由變量e指向狐肢。
    • 新舊值替換添吗,根據(jù)函數(shù)一開始傳入的參數(shù)onlyIfAbsent或者oldValue的特殊情況來(lái)決定是否替換。
      • key對(duì)應(yīng)的舊值如果為NULL份名,那么無(wú)論onlyIfAbsent是否決定替換碟联。都將被替換。
      • key對(duì)應(yīng)的舊值如果不為NULL僵腺,那么如果onlyIfAbsent是false就替換鲤孵。
    • 最后返回被替換掉的值。
  • 修改了實(shí)例結(jié)構(gòu)所以modCount+1辰如。
  • 加入元素后判斷元素個(gè)數(shù)是否超過(guò)了閾值普监。如果是resize函數(shù)擴(kuò)桶數(shù)

這樣我們的putVal函數(shù)其實(shí)思路就理清了,看上去很復(fù)雜琉兜,其實(shí)宗旨沒變凯正,也就是判斷,擴(kuò)容豌蟋,找位置廊散,去除特殊情況,元素插入梧疲,只不過(guò)數(shù)據(jù)結(jié)構(gòu)的差異性導(dǎo)致其特殊情況較多允睹,判斷的復(fù)雜了些运准。接下來(lái)該分析resize函數(shù)了。我們可以明顯的看到putVal函數(shù)多出調(diào)用了resize函數(shù)缭受。所以要想徹底理清HashMap實(shí)例是如何構(gòu)建的胁澳,resize函數(shù)必然需要了解一下。

目前對(duì)resize的了解:table==null時(shí)贯涎,調(diào)用resize初始化table听哭。元素?cái)?shù)量>=threshold時(shí),調(diào)用resize擴(kuò)容塘雳。

Node<K,V>[] resize()

/**
 * Initializes or doubles table size.  If null, allocates in
 * accord with initial capacity target held in field threshold.
 * Otherwise, because we are using power-of-two expansion, the
 * elements from each bin must either stay at same index, or move
 * with a power of two offset in the new table
 */
final Node<K,V>[] resize() {
}

從該函數(shù)的開頭我們也可以確認(rèn)我們剛才的觀點(diǎn)陆盘。用來(lái)初始化和給table擴(kuò)容的。

總結(jié)下來(lái)就是:

哈細(xì)表擴(kuò)容:

  • 當(dāng)舊表容量大于0時(shí)败明,即舊表被初始化過(guò)了
    • 并且如果其容量大于或等于int的最大值隘马。那么閾值設(shè)置為int的最大值。并返回舊舊表妻顶,此時(shí)表已經(jīng)不能再擴(kuò)容了酸员,能做的只有置閾值為其能到大的最大值,盡可能的減少擴(kuò)容次數(shù)讳嘱。
    • 當(dāng)舊表容量不及最大容量的一半時(shí)幔嗦,并且舊表容量大于默認(rèn)容量16時(shí)。由于函數(shù)resize被調(diào)用了沥潭。所以需要滿足外界擴(kuò)容需求邀泉,舊表閾值<<1。
  • 當(dāng)舊表沒被初始化钝鸽,但HashMap實(shí)例被初始化了汇恤。
    • 否則當(dāng)舊表還未被初始化時(shí),只是在初始化HashMap實(shí)例的時(shí)候賦予了閾值和負(fù)載因子(結(jié)合構(gòu)造函數(shù)思考)拔恰,當(dāng)舊閾值>0時(shí)因谎,哈希表初始容量設(shè)置為閾值。
    • 否則颜懊,當(dāng)構(gòu)造HashMap實(shí)例時(shí)如果連閾值也沒有賦值(默認(rèn)為0)只是初始化了負(fù)載因子的話(默認(rèn)無(wú)參構(gòu)造函數(shù))财岔。初始化哈希表容量為DEFAULT_INITIAL_CAPACITY即16,閾值為DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY即12河爹。
  • 當(dāng)閾值=0時(shí)匠璧,新閾值=新容量*負(fù)載因子。當(dāng)HashMap實(shí)例被被初始化時(shí)閾值也被初始化時(shí)會(huì)走這一分支
  • 最后確定容量和閾值后初始化哈細(xì)表昌抠。

數(shù)據(jù)遷移:

  • 當(dāng)舊哈細(xì)表存在時(shí)患朱,迭代哈細(xì)表每個(gè)元素。因?yàn)槊總€(gè)元素中存的都是鏈表或者紅黑樹炊苫。根據(jù)每個(gè)元素的哈希值重新散列到新哈希表的對(duì)應(yīng)位置上裁厅。底層數(shù)據(jù)結(jié)構(gòu)和節(jié)點(diǎn)的存儲(chǔ)位置很大程度上會(huì)更改冰沙。

get(Object key)

從注釋中可以看出,返回 null 值并不一定 表明該映射不包含該鍵的映射關(guān)系执虹;也可能該映射將該鍵顯示地映射為 null拓挥。可使用 containsKey 操作來(lái)區(qū)分這兩種情況袋励。

 /**
 * ·····
 * <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.
 * ·····
 */
public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

函數(shù)具體查詢邏輯封裝到了getNode函數(shù)侥啤。


簡(jiǎn)單來(lái)說(shuō):

  • key對(duì)應(yīng)的哈希值計(jì)算出來(lái)落到哈希表對(duì)應(yīng)的地方上,如果有元素存在茬故。
  • 有元素存在就需要比較了盖灸,因?yàn)樵厣峡赡艽娴氖擎湵砘蛘呒t黑樹。
    • 第一個(gè)元素就是搜尋的key本身
    • 第一個(gè)不是磺芭,但其是紅黑樹結(jié)構(gòu)赁炎。參數(shù)傳遞并去紅黑樹中找
    • 不是紅黑樹結(jié)構(gòu),那么就是鏈表結(jié)構(gòu)钾腺,進(jìn)行迭代尋找徙垫,獲取key對(duì)應(yīng)的value。

remove(Object key)

/**
 * Removes the mapping for the specified key from this map if present.
 *
 * @param  key key whose mapping is to be removed from the map
 * @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 remove(Object key) {
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}

從函數(shù)的注釋中可以看出放棒,該函數(shù)刪除key對(duì)應(yīng)的Entry并返回與 key 關(guān)聯(lián)的舊值姻报;如果 key 沒有任何映射關(guān)系,則返回 null间螟。但需要注意的是: 返回 null 還可能表示該映射之前將 null 與 key 關(guān)聯(lián)吴旋。

函數(shù)再次把元素的通過(guò)hash函數(shù)包裝傳了進(jìn)去,這很好理解寒亥,因?yàn)榈讓邮枪1砺镉矢贿@樣怎么能找到key到底在哈希表什么位置呢荧关。我們可以看到刪除邏輯的具體邏輯封裝到了removeNode函數(shù)中溉奕。

removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable)

先來(lái)對(duì)removeNod函數(shù)的多個(gè)參數(shù)進(jìn)行一番分析。

 * @param hash hash for key 
 * @param key the key
 * @param value the value to match if matchValue, else ignored
 * @param matchValue if true only remove if value is equal
 * @param movable if false do not move other nodes while removing
  1. hash(key)
  2. key
  3. 對(duì)應(yīng)matchValue參數(shù)忍啤,如果matchValue為true加勤。那么刪除key時(shí)其value必須與value相等,否則跳過(guò)這次刪除
  4. 如果true同波。只有當(dāng)值相等時(shí)才刪除鳄梅。
  5. 如果為false,那么在刪除節(jié)點(diǎn)時(shí)不移動(dòng)其他節(jié)點(diǎn)未檩。

總的來(lái)說(shuō):

  • 先判斷key是否可以映射到哈希表上戴尸。

    • 如果可以,判斷一下映射的位置頭元素是否為該查找的元素冤狡。
    • 如果頭元素不是要找元素孙蒙,判斷一下頭元素后有無(wú)其他元素项棠。如果有再判斷一下這個(gè)元素的數(shù)據(jù)結(jié)構(gòu)是否為紅黑樹,如果是挎峦,用node變量指向該key對(duì)應(yīng)在紅黑樹中的節(jié)點(diǎn)香追。
    • 如果不是那說(shuō)明哈希表該索引處是一個(gè)鏈表結(jié)構(gòu),然后對(duì)鏈表元素迭代尋找該元素坦胶,并用node節(jié)點(diǎn)指向?qū)ふ业降脑撛赝傅洹P枰⒁獾氖牵?code>p變量的邏輯定位,由于do while循環(huán)體中break語(yǔ)句相對(duì)于p=e語(yǔ)句置前顿苇,p變量在循環(huán)退出時(shí)指向的是node節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)峭咒。
  • 經(jīng)歷過(guò)橘黃色和紅色兩大塊邏輯判斷,成功找到了待刪除節(jié)點(diǎn)的位置纪岁,并用node節(jié)點(diǎn)指向讹语。如果該Node節(jié)點(diǎn)被成功找到了的話。

    • 紅黑樹:如果node節(jié)點(diǎn)所在哈希表的位置元素的存儲(chǔ)結(jié)構(gòu)式是紅黑樹的話蜂科。調(diào)用紅黑樹的刪除方法
    • 鏈表:如果頭節(jié)點(diǎn)就是待刪除節(jié)點(diǎn)的話顽决,那么待刪除節(jié)點(diǎn)的next節(jié)點(diǎn)做頭結(jié)點(diǎn)
    • 鏈表:如果待刪除節(jié)點(diǎn)是鏈表中間部分的某個(gè)節(jié)點(diǎn)。借助p變量進(jìn)行刪除导匣。
  • modCount+1才菠,size-1,并將刪除的節(jié)點(diǎn)返回贡定。

HashMap與HashTable對(duì)比

我們從一開始就曾說(shuō)赋访,兩者從存儲(chǔ)結(jié)構(gòu)和實(shí)現(xiàn)來(lái)講基本上都是相同的。兩者最大的差別就是HashTable是線程安全的缓待,而HashMap是線程不安全的蚓耽。并且在數(shù)據(jù)存儲(chǔ)時(shí),HashMap允許key和value為null旋炒。而HashTable則鍵值均不允許為null步悠。并且HashTable規(guī)定用作鍵的對(duì)象必須實(shí)現(xiàn) hashCode 方法和 equals 方法。

HashTable在Map映射中的地位有點(diǎn)兒像Vector在List集合中的定位瘫镇。也是比較過(guò)時(shí)的類鼎兽,設(shè)計(jì)上有一些缺陷,不需要線程安全的情況下可以使用HashMap替代它铣除,而需要線程安全的情況下也可以用ConcurrentHashMap來(lái)替代它谚咬。

總結(jié)

  • HashMap的底層是:數(shù)組+鏈表+紅黑樹
  • HashMap的實(shí)例有兩個(gè)重要參數(shù)影響其性能。負(fù)載因子和初始容量尚粘,(裝載因子*初始容量)< 散列表元素時(shí)择卦,該散列表會(huì)擴(kuò)容2倍,并重散列。
    • 負(fù)載因子:默認(rèn)0.75秉继,無(wú)論是初始大了還是初始小了對(duì)HashMap的性能都不好
      • 初始值大了潘明,可以減少散列表再散列(擴(kuò)容的次數(shù)),但同時(shí)會(huì)導(dǎo)致散列沖突的可能性變大(散列沖突也是耗性能的一個(gè)操作秕噪,要得操作鏈表(紅黑樹)
      • 初始值小了钳降,可以減小散列沖突的可能性,但同時(shí)擴(kuò)容的次數(shù)可能就會(huì)變多腌巾!
    • 初始容量:默認(rèn)值是16遂填,無(wú)論初始大了還是小了對(duì)HashMap都是有影響的
      • 初始容量過(guò)大,那么遍歷時(shí)速度就會(huì)受影響~
      • 初始容量過(guò)小澈蝙,散列表再散列(擴(kuò)容的次數(shù))可能就變得多吓坚,擴(kuò)容也是一件非常耗費(fèi)性能的一件事
    • 參考公式:閾值 = 容量 * 負(fù)載因子
  • HashMap在計(jì)算元素在哈希表中存儲(chǔ)位置的時(shí)候,并不是直接拿key的哈希值來(lái)用的灯荧,會(huì)先對(duì)key的哈希值進(jìn)行右移16位礁击,然后再與其key的哈希值進(jìn)行異或操作。根本上就是高位不變逗载,低位變?yōu)楦?6位和低16位的異或結(jié)果哆窿,使得元素在存儲(chǔ)時(shí)的隨機(jī)性更大了。分布更加均勻了厉斟,減少了哈希沖突發(fā)生的概率
  • 并不是箱子上元素大于等于8位元素的時(shí)候該哈希表的元素底層就能變成紅黑樹挚躯,它得同時(shí)滿足哈希表總?cè)萘看笥?4才行,同時(shí)當(dāng)紅黑樹元素?cái)?shù)量低于8時(shí)擦秽,并不是會(huì)立馬變回鏈表码荔。而是在其元素小于6時(shí)才會(huì)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末感挥,一起剝皮案震驚了整個(gè)濱河市缩搅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌触幼,老刑警劉巖硼瓣,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異域蜗,居然都是意外死亡巨双,警方通過(guò)查閱死者的電腦和手機(jī)噪猾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門霉祸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人袱蜡,你說(shuō)我怎么就攤上這事丝蹭。” “怎么了坪蚁?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵奔穿,是天一觀的道長(zhǎng)镜沽。 經(jīng)常有香客問我,道長(zhǎng)贱田,這世上最難降的妖魔是什么缅茉? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮男摧,結(jié)果婚禮上蔬墩,老公的妹妹穿的比我還像新娘。我一直安慰自己耗拓,他們只是感情好拇颅,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著乔询,像睡著了一般樟插。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上竿刁,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天黄锤,我揣著相機(jī)與錄音,去河邊找鬼食拜。 笑死猜扮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的监婶。 我是一名探鬼主播旅赢,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼惑惶!你這毒婦竟也來(lái)了煮盼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤带污,失蹤者是張志新(化名)和其女友劉穎僵控,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鱼冀,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡报破,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了千绪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片充易。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖荸型,靈堂內(nèi)的尸體忽然破棺而出盹靴,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布稿静,位于F島的核電站梭冠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏改备。R本人自食惡果不足惜控漠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望悬钳。 院中可真熱鬧润脸,春花似錦、人聲如沸他去。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)灾测。三九已至爆价,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間媳搪,已是汗流浹背铭段。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秦爆,地道東北人序愚。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像等限,于是被迫代替她去往敵國(guó)和親爸吮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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

  • HashMap 源碼分析 前幾篇分析了 ArrayList 望门, LinkedList 形娇,Vector ,Stack...
    醒著的碼者閱讀 2,849評(píng)論 4 44
  • HashMap 是 Java 面試必考的知識(shí)點(diǎn)筹误,面試官?gòu)倪@個(gè)小知識(shí)點(diǎn)就可以了解我們對(duì) Java 基礎(chǔ)的掌握程度桐早。網(wǎng)...
    野狗子嗷嗷嗷閱讀 6,669評(píng)論 9 107
  • 2016年因?yàn)閷⑴d趣重心放在了讀書寫文上,畫畫數(shù)量銳減厨剪,捂臉哄酝。 因?yàn)槲沂莻€(gè)桂花控,去年九月份畫了一幅桂花圖后陸陸續(xù)...
    葉薄荷閱讀 2,578評(píng)論 64 128
  • 自由,是個(gè)耳熟能詳?shù)脑~钾唬,卻不是每個(gè)人都了解它万哪。 每天侠驯,大家都說(shuō)好累啊好苦啊抡秆,好想要自由奕巍。 你真的是一個(gè)配得上自由的...
    顏鹵煮閱讀 1,811評(píng)論 4 50
  • 過(guò)去的慣性的止,仍然讓自己向前滑行。 在過(guò)去的時(shí)間里面着撩,有好些次诅福,自己開始意識(shí)到時(shí)間過(guò)了好些年,而自己的記憶對(duì)過(guò)去的這...
    獸醫(yī)博士在廣西閱讀 216評(píng)論 2 5