HashMap-1.7

目錄

一.簡介

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

三.具體使用

四.基礎(chǔ)知識

五.源碼分析

六.源碼總結(jié)

七.與jdk1.8的區(qū)別

八.額外補充:關(guān)于HashMap的其他問題

九.總結(jié)


一 簡介

?? 類定義

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

主要介紹


???HashMap的實現(xiàn)在JDK1.7和JDK1.8差別較大

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

? 具體描述

HashMap 采用的數(shù)據(jù)結(jié)構(gòu) : 數(shù)組(主) + 單鏈表(副)

該數(shù)據(jù)結(jié)構(gòu)方式也稱:拉鏈法(鏈路法)‘


? 存儲流程


put方法簡單版

? 數(shù)組元素 & 鏈表節(jié)點的 實現(xiàn)類

HashMap中的數(shù)組元素&鏈表節(jié)點 采用 Entry 類實現(xiàn)

數(shù)組的下邊:key值hash后&數(shù)組長度-1

1.HashMap的本質(zhì)=1個存儲Entry類對象的數(shù)組+多個單鏈表

2.Entry對象本質(zhì)=1個映射(鍵-值)觅够,屬性包括:鍵(key)灭红、值(value) & 下1節(jié)點(next)=單鏈表的指針=也是一個Entry對象,用于解決hash沖突

三 具體使用

? 主要使用的API(方法走趋、函數(shù))

V get(Object key); // 獲得指定鍵的值

V put(K key, V value);? // 添加鍵值對

void putAll(Map<? extends K, ? extends V> m);? // 將指定Map中的鍵值對 復(fù)制到 此Map中

V remove(Object key);? // 刪除該鍵值對

boolean containsKey(Object key); // 判斷是否存在該鍵的鍵值對助泽;是 則返回true

boolean containsValue(Object value);? // 判斷是否存在該值的鍵值對楞慈;是 則返回true

Set<K> keySet();? // 單獨抽取key序列旺嬉,將所有key生成一個Set

Collection<V> values();? // 單獨value序列败潦,將所有value生成一個Collection

void clear(); // 清除哈希表中的所有鍵值對

int size();? // 返回哈希表中所有 鍵值對的數(shù)量 = 數(shù)組中的鍵值對 + 鏈表中的鍵值對

boolean isEmpty(); // 判斷HashMap是否為空本冲;size == 0時 表示為 空

?使用

import java.util.Collection;

import java.util.HashMap;

import java.util.Iterator;

import java.util.Map;

import java.util.Set;

public class HashMapTest {

? ? public static void main(String[] args) {

? ? ? /**

? ? ? ? * 1. 聲明1個 HashMap的對象

? ? ? ? */

? ? ? ? Map<String, Integer> map = new HashMap<String, Integer>();

? ? ? /**

? ? ? ? * 2. 向HashMap添加數(shù)據(jù)(成對 放入 鍵 - 值對)

? ? ? ? */

? ? ? ? map.put("Android", 1);

? ? ? ? map.put("Java", 2);

? ? ? ? map.put("iOS", 3);

? ? ? ? map.put("數(shù)據(jù)挖掘", 4);

? ? ? ? map.put("產(chǎn)品經(jīng)理", 5);

? ? ? /**

? ? ? ? * 3. 獲取 HashMap 的某個數(shù)據(jù)

? ? ? ? */

? ? ? ? System.out.println("key = 產(chǎn)品經(jīng)理時的值為:" + map.get("產(chǎn)品經(jīng)理"));

? ? ? /**

? ? ? ? * 4. 獲取 HashMap 的全部數(shù)據(jù):遍歷HashMap

? ? ? ? * 核心思想:

? ? ? ? * 步驟1:獲得key-value對(Entry) 或 key 或 value的Set集合

? ? ? ? * 步驟2:遍歷上述Set集合(使用for循環(huán) 准脂、 迭代器(Iterator)均可)

? ? ? ? * 方法共有3種:分別針對 key-value對(Entry) 或 key 或 value

? ? ? ? */

? ? ? ? // 方法1:獲得key-value的Set集合 再遍歷

? ? ? ? System.out.println("方法1");

? ? ? ? // 1. 獲得key-value對(Entry)的Set集合

? ? ? ? Set<Map.Entry<String, Integer>> entrySet = map.entrySet();

? ? ? ? // 2. 遍歷Set集合,從而獲取key-value

? ? ? ? // 2.1 通過for循環(huán)

? ? ? ? for(Map.Entry<String, Integer> entry : entrySet){

? ? ? ? ? ? System.out.print(entry.getKey());

? ? ? ? ? ? System.out.println(entry.getValue());

? ? ? ? }

? ? ? ? System.out.println("----------");

? ? ? ? // 2.2 通過迭代器:先獲得key-value對(Entry)的Iterator眼俊,再循環(huán)遍歷

? ? ? ? Iterator iter1 = entrySet.iterator();

? ? ? ? while (iter1.hasNext()) {

? ? ? ? ? ? // 遍歷時意狠,需先獲取entry粟关,再分別獲取key疮胖、value

? ? ? ? ? ? Map.Entry entry = (Map.Entry) iter1.next();

? ? ? ? ? ? System.out.print((String) entry.getKey());

? ? ? ? ? ? System.out.println((Integer) entry.getValue());

? ? ? ? }

? ? ? ? // 方法2:獲得key的Set集合 再遍歷

? ? ? ? System.out.println("方法2");

? ? ? ? // 1. 獲得key的Set集合

? ? ? ? Set<String> keySet = map.keySet();

? ? ? ? // 2. 遍歷Set集合,從而獲取key闷板,再獲取value

? ? ? ? // 2.1 通過for循環(huán)

? ? ? ? for(String key : keySet){

? ? ? ? ? ? System.out.print(key);

? ? ? ? ? ? System.out.println(map.get(key));

? ? ? ? }

? ? ? ? System.out.println("----------");

? ? ? ? // 2.2 通過迭代器:先獲得key的Iterator澎灸,再循環(huán)遍歷

? ? ? ? Iterator iter2 = keySet.iterator();

? ? ? ? String key = null;

? ? ? ? while (iter2.hasNext()) {

? ? ? ? ? ? key = (String)iter2.next();

? ? ? ? ? ? System.out.print(key);

? ? ? ? ? ? System.out.println(map.get(key));

? ? ? ? }

? ? ? ? // 方法3:獲得value的Set集合 再遍歷

? ? ? ? System.out.println("方法3");

? ? ? ? // 1. 獲得value的Set集合

? ? ? ? Collection valueSet = map.values();

? ? ? ? // 2. 遍歷Set集合,從而獲取value

? ? ? ? // 2.1 獲得values 的Iterator

? ? ? ? Iterator iter3 = valueSet.iterator();

? ? ? ? // 2.2 通過遍歷遮晚,直接獲取value

? ? ? ? while (iter3.hasNext()) {

? ? ? ? ? ? System.out.println(iter3.next());

? ? ? ? }

? ? }

}

// 注:對于遍歷方式性昭,推薦使用針對 key-value對(Entry)的方式:效率高

// 原因(此處存在疑問:看了源碼keySet、valueSet相對于entrySet只多了一個獲取值得操作):

? // 1. 對于 遍歷keySet 县遣、valueSet糜颠,實質(zhì)上 = 遍歷了2次:1 = 轉(zhuǎn)為 iterator 迭代器遍歷、2 = 從 HashMap 中取出 key 的 value 操作(通過 key 值 hashCode 和 equals 索引)

? // 2. 對于 遍歷 entrySet 萧求,實質(zhì) = 遍歷了1次 = 獲取存儲實體Entry(存儲了key 和 value )

四 基礎(chǔ)知識

? HashMap中的重要參數(shù)(變量)

? 具體介紹

/*

*1.容量(capacity):HashMap中數(shù)組的長度

*a.容量范圍:必須是2的冪 & <最大容量(2的30次方)

*b.初始容量=哈希表創(chuàng)建時的容量

*默認容量 = 16 = 1<<4 = 00001中的1向左移4位 = 10000 = 十進制的2^4 = 16

*/

static final int DEFAULT_INITIAL_CAPACITY =1 << 4;

//最大容量 = 2的30次方(若傳入的容量過大其兴,將被最大值替換)

static final int MAXIMUM_CAPACITY =1 <<30;

/*

*2.加載因子(load factor):限制HashMap的容量,當(dāng)容量超過 負載因子 * ?哈希表的現(xiàn)有容量 * <= 添加元素后的容量夸政,擴容

*/

//實際加載因子

final float loadFactor;

//默認加載因子=0.75

static final float DEFAULT_LOAD_FACTOR =0.75f;

//3.擴容閾值(threshold):當(dāng)哈希表的大小 >=擴容閾值 時元旬,就會擴容哈希表

//擴容閾值 = 容量 * 加載因子

int threshold;

//HashMap 的實現(xiàn)方式 = 拉鏈法,Entry數(shù)組上的每個元素本質(zhì)上是一個單鏈表

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

//HashMap的大小守问,即 HashMap中存儲的鍵值對的數(shù)量

transient int size;

? 詳細說明 加載因子


HashMap 加載因子 詳解

五 源碼分析

? 聲明一個HashMap的對象

/**

? * 函數(shù)使用原型

? */

? Map<String,Integer> map = new HashMap<String,Integer>();

/**

? * 源碼分析:主要是HashMap的構(gòu)造函數(shù) = 4個

? * 僅貼出關(guān)于HashMap構(gòu)造函數(shù)的源碼

? */

? public class HashMap<K,V>

? ? ? extends AbstractMap<K,V>

? ? ? implements Map<K,V>, Cloneable, Serializable{

? ? // 省略上節(jié)闡述的參數(shù)


? /**

? ? * 構(gòu)造函數(shù)1:默認構(gòu)造函數(shù)(無參)

? ? * 加載因子 & 容量 = 默認 = 0.75匀归、16

? ? */

? ? public HashMap() {

? ? ? ? // 實際上是調(diào)用構(gòu)造函數(shù)3:指定“容量大小”和“加載因子”的構(gòu)造函數(shù)

? ? ? ? // 傳入的指定容量 & 加載因子 = 默認

? ? ? ? this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);

? ? }

? ? /**

? ? * 構(gòu)造函數(shù)2:指定“容量大小”的構(gòu)造函數(shù)

? ? * 加載因子 = 默認 = 0.75 、容量 = 指定大小

? ? */

? ? public HashMap(int initialCapacity) {

? ? ? ? // 實際上是調(diào)用指定“容量大小”和“加載因子”的構(gòu)造函數(shù)

? ? ? ? // 只是在傳入的加載因子參數(shù) = 默認加載因子

? ? ? ? this(initialCapacity, DEFAULT_LOAD_FACTOR);


? ? }

? ? /**

? ? * 構(gòu)造函數(shù)3:指定“容量大小”和“加載因子”的構(gòu)造函數(shù)

? ? * 加載因子 & 容量 = 自己指定

? ? */

? ? public HashMap(int initialCapacity, float loadFactor) {

? ? ? ? // HashMap的最大容量只能是MAXIMUM_CAPACITY耗帕,哪怕傳入的 > 最大容量

? ? ? ? if (initialCapacity > MAXIMUM_CAPACITY)

? ? ? ? ? ? initialCapacity = MAXIMUM_CAPACITY;

? ? ? ? // 設(shè)置 加載因子

? ? ? ? this.loadFactor = loadFactor;

? ? ? ? // 設(shè)置 擴容閾值 = 初始容量

? ? ? ? // 注:此處不是真正的閾值穆端,是為了擴展table,該閾值后面會重新計算仿便,下面會詳細講解?

? ? ? ? threshold = initialCapacity;?

? ? ? ? init(); // 一個空方法用于未來的子對象擴展

? ? }

? ? /**

? ? * 構(gòu)造函數(shù)4:包含“子Map”的構(gòu)造函數(shù)

? ? * 即 構(gòu)造出來的HashMap包含傳入Map的映射關(guān)系

? ? * 加載因子 & 容量 = 默認

? ? */

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

? ? ? ? // 設(shè)置容量大小 & 加載因子 = 默認

? ? ? ? this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,

? ? ? ? ? ? ? ? DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);

? ? ? ? // 該方法用于初始化 數(shù)組 & 閾值体啰,下面會詳細說明

? ? ? ? inflateTable(threshold);

? ? ? ? // 將傳入的子Map中的全部元素逐個添加到HashMap中

? ? ? ? putAllForCreate(m);

? ? }

}

注:

1.此處僅用于接收初始容量大小(capacity)、加載因子(load factor),但仍無真正初始化哈希表探越,及初始化存儲數(shù)組table狡赐;

2.此處結(jié)論:真正初始化哈希表(初始化存儲數(shù)組table)是在第一次添加鍵值對時,即 第一次 調(diào)用 put()時

? 向HashMap添加數(shù)據(jù)

詳細流程如下:

HashMap(1.7) put()-詳版

? put源碼

/**

? ? * 源碼分析:主要分析: HashMap的put函數(shù)

? ? */

? ? public V put(K key, V value)

(分析1)// 1. 若 哈希表未初始化(即 table為空)

? ? ? ? // 則使用 構(gòu)造函數(shù)時設(shè)置的閾值(即初始容量) 初始化 數(shù)組table?

? ? ? ? if (table == EMPTY_TABLE) {

? ? ? ? inflateTable(threshold);

? ? }?

? ? ? ? // 2. 判斷key是否為空值null

(分析2)// 2.1 若key == null钦幔,則將該鍵-值 存放到數(shù)組table 中的第1個位置枕屉,即table [0]

? ? ? ? // (本質(zhì):key = Null時,hash值 = 0鲤氢,故存放到table[0]中)

? ? ? ? // 該位置永遠只有1個value搀擂,新傳進來的value會覆蓋舊的value

? ? ? ? if (key == null)

? ? ? ? ? ? return putForNullKey(value);

(分析3) // 2.2 若 key ≠ null西潘,則計算存放數(shù)組 table 中的位置(下標(biāo)、索引)

? ? ? ? // a. 根據(jù)鍵值key計算hash值

? ? ? ? int hash = hash(key);

? ? ? ? // b. 根據(jù)hash值 最終獲得 key對應(yīng)存放的數(shù)組Table中位置

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

? ? ? ? // 3. 判斷該key對應(yīng)的值是否已存在(通過遍歷 以該數(shù)組元素為頭結(jié)點的鏈表 逐個判斷)

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

? ? ? ? ? ? Object k;

(分析4)// 3.1 若該key已存在(即 key-value已存在 )哨颂,則用 新value 替換 舊value

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

? ? ? ? ? ? ? ? V oldValue = e.value;

? ? ? ? ? ? ? ? e.value = value;

? ? ? ? ? ? ? ? e.recordAccess(this);

? ? ? ? ? ? ? ? return oldValue; //并返回舊的value

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? modCount++;

(分析5)// 3.2 若 該key不存在喷市,則將“key-value”添加到table中

? ? ? ? addEntry(hash, key, value, i);

? ? ? ? return null;

? ? }

? 初始化哈希表-初始化數(shù)組

/**

? ? * 函數(shù)使用原型

? ? */

? ? ? if (table == EMPTY_TABLE) {

? ? ? ? inflateTable(threshold);

? ? }?

? /**

? ? * 源碼分析:inflateTable(threshold);

? ? */

? ? private void inflateTable(int toSize) {?


? ? // 1. 將傳入的容量大小轉(zhuǎn)化為:>傳入容量大小的最小的2的次冪

? ? // 即如果傳入的是容量大小是19,那么轉(zhuǎn)化后威恼,初始化容量大小為32(即2的5次冪)

? ? int capacity = roundUpToPowerOf2(toSize);->>分析1?

? ? // 2. 重新計算閾值 threshold = 容量 * 加載因子?

? ? threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);?

? ? // 3. 使用計算后的初始容量(已經(jīng)是2的次冪) 初始化數(shù)組table(作為數(shù)組長度)

? ? // 即 哈希表的容量大小 = 數(shù)組大衅沸铡(長度)

? ? table = new Entry[capacity]; //用該容量初始化table?

? ? initHashSeedAsNeeded(capacity);?

}?

? ? /**

? ? * 分析1:roundUpToPowerOf2(toSize)

? ? * 作用:將傳入的容量大小轉(zhuǎn)化為:>傳入容量大小的最小的2的冪

? ? * 特別注意:容量大小必須為2的冪,該原因在下面的講解會詳細分析

? ? */

? ? private static int roundUpToPowerOf2(int number) {?


? ? ? //若 容量超過了最大值箫措,初始化容量設(shè)置為最大值 腹备;否則,設(shè)置為:>傳入容量大小的最小的2的次冪

? ? ? return number >= MAXIMUM_CAPACITY? ?

? ? ? ? ? ? MAXIMUM_CAPACITY? : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;

? PUT源碼

/**

? ? * 函數(shù)使用原型

? ? */

? ? ? if (key == null)

? ? ? ? ? return putForNullKey(value);

? /**

? ? * 源碼分析:putForNullKey(value)

? ? */

? ? ? private V putForNullKey(V value) {?

? ? ? ? // 遍歷以table[0]為首的鏈表斤蔓,尋找是否存在key==null 對應(yīng)的鍵值對

? ? ? ? // 1. 若有:則用新value 替換 舊value植酥;同時返回舊的value值

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

? ? ? ? ? if (e.key == null) {?

? ? ? ? ? ? V oldValue = e.value;?

? ? ? ? ? ? e.value = value;?

? ? ? ? ? ? e.recordAccess(this);?

? ? ? ? ? ? return oldValue;?

? ? ? ? }?

? ? }?

? ? modCount++;?

? ? // 2 .若無key==null的鍵,那么調(diào)用addEntry()弦牡,將空鍵 & 對應(yīng)的值封裝到Entry中友驮,并放到table[0]中

? ? addEntry(0, null, value, 0);

? ? // 注:

? ? // a. addEntry()的第1個參數(shù) = hash值 = 傳入0

? ? // b. 即 說明:當(dāng)key = null時,也有hash值 = 0驾锰,所以HashMap的key 可為null

? ? // c. 對比HashTable卸留,由于HashTable對key直接hashCode(),若key為null時稻据,會拋出異常艾猜,所以HashTable的key不可為null

? ? // d. 此處只需知道是將 key-value 添加到HashMap中即可,關(guān)于addEntry()的源碼分析將等到下面再詳細說明捻悯,

? ? return null;?

} ??

?分析

為什么不直接采用經(jīng)過hashCode()處理的哈希嗎作為存儲數(shù)組table的下標(biāo)位置匆赃?


原因

為什么采用 哈希碼 & (數(shù)組長度-1) 計算數(shù)組下標(biāo)?


描述

引用:https://blog.csdn.net/carson_ho/article/details/79373026

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末今缚,一起剝皮案震驚了整個濱河市算柳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌姓言,老刑警劉巖瞬项,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異何荚,居然都是意外死亡囱淋,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門餐塘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妥衣,“玉大人,你說我怎么就攤上這事∷笆郑” “怎么了蜂筹?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長芦倒。 經(jīng)常有香客問我艺挪,道長,這世上最難降的妖魔是什么兵扬? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任麻裳,我火速辦了婚禮,結(jié)果婚禮上周霉,老公的妹妹穿的比我還像新娘掂器。我一直安慰自己,他們只是感情好俱箱,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著灭必,像睡著了一般狞谱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上禁漓,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天跟衅,我揣著相機與錄音,去河邊找鬼播歼。 笑死伶跷,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的秘狞。 我是一名探鬼主播叭莫,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼烁试!你這毒婦竟也來了雇初?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤减响,失蹤者是張志新(化名)和其女友劉穎靖诗,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體支示,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡刊橘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了颂鸿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片促绵。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绞愚,到底是詐尸還是另有隱情叙甸,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布位衩,位于F島的核電站裆蒸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏糖驴。R本人自食惡果不足惜僚祷,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贮缕。 院中可真熱鬧辙谜,春花似錦、人聲如沸感昼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽定嗓。三九已至蜕琴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宵溅,已是汗流浹背凌简。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留恃逻,地道東北人雏搂。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像寇损,于是被迫代替她去往敵國和親凸郑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

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