HashMap面試問題整理

1. HashMap的實(shí)現(xiàn)

1.1 Hash的實(shí)現(xiàn)

image.png

1.2. 底層實(shí)現(xiàn)

1.2.1. JDK1.8之前

拉鏈法
創(chuàng)建一個(gè)鏈表數(shù)組,數(shù)組中每一格就是一個(gè)鏈表。若遇到哈希沖突,則將沖突的值加到鏈表中即可。

圖源見水印

1.2.2. JDK1.8之后

鏈表長度大于閾值(默認(rèn)為8)時(shí),將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時(shí)間。

圖源:https://thinkwon.blog.csdn.net

2. Jdk 1.7和1.8 hashMap 區(qū)別

2.1 擴(kuò)容時(shí)方式不同

  • 在移動(dòng)鏈表時(shí),1.7是頭插法,1.8尾插法,導(dǎo)致1.7鏈表元素順序會(huì)顛倒,1.8不會(huì)

  • 確定元素新的索引時(shí),1.8只需要看多出來的一位index是0還是1,1.7需要重新計(jì)算h&(length - 1)

2.1.1 頭插法和尾插法

頭插

新增在鏈表上元素的位置為鏈表頭部,也就是數(shù)組桶位上的那個(gè)位置

可能是考慮到熱點(diǎn)數(shù)據(jù)的原因,即最近插入的元素也很可能最近會(huì)被使用到

尾插

為了避免出現(xiàn)逆序?qū)е碌某森h(huán)的問題

2.1.2 頭插法導(dǎo)致的后果(1.7并發(fā)為何不安全)

transfer()方法中,因?yàn)樾碌?code>Table順序和舊的不同,所以在多線程同時(shí)擴(kuò)容情況下,會(huì)導(dǎo)致第二個(gè)擴(kuò)容的線程next混亂,本來是A -> B,但t1線程已經(jīng)B -> A了,所以就成環(huán)了。

2.1.3 Java1.8如何解決成環(huán)的問題的

1.8扔掉了transfer()方法,用resize()擴(kuò)容:

使用do while循環(huán)一次將一個(gè)鏈表上的所有元素加到鏈表上,然后再放到新的Table上對應(yīng)的索引位置。

2.2 底層結(jié)構(gòu)不同

JDK1.7的時(shí)候使用的是數(shù)組+ 單鏈表的數(shù)據(jù)結(jié)構(gòu)。但是在JDK1.8及之后時(shí),使用的是數(shù)組+鏈表+紅黑樹的數(shù)據(jù)結(jié)構(gòu)(當(dāng)鏈表的深度達(dá)到8的時(shí)候,也就是默認(rèn)閾值,就會(huì)自動(dòng)擴(kuò)容把鏈表轉(zhuǎn)成紅黑樹的數(shù)據(jù)結(jié)構(gòu)來把時(shí)間復(fù)雜度從O(n)變成O(logN)提高了效率)

2.3 索引計(jì)算方式不同

1.8的索引 只用了一次移位,一次位運(yùn)算就確定了索引,計(jì)算過程優(yōu)化。

二者的hash擾動(dòng)函數(shù)也不同,1.7有4次移位和5次位運(yùn)算,1.8只有一次移位和一次位運(yùn)算

3. HashMap參數(shù)理解

3.1 常見參數(shù)

初始容量和加載因子會(huì)影響HashMap的性能:

  1. 初始容量設(shè)置過小會(huì)導(dǎo)致大量擴(kuò)容(最好預(yù)估一下)
  2. 加載因子設(shè)置不當(dāng),導(dǎo)致頻繁擴(kuò)容操作

3.1.1 capacity

常說的capacity指的是DEFAULT_INITIAL_CAPACITY(初始容量),值是1<<4,即16;

capacity()是個(gè)方法,返回?cái)?shù)組的長度。

final int capacity() {
    return (table != null) ? table.length :
    (threshold > 0) ? threshold :
    DEFAULT_INITIAL_CAPACITY;
}

3.1.2 loadFactor(加載因子)

final float loadFactor;

在hashMap構(gòu)造函數(shù)中,賦值為DEFAULT_LOAD_FACTOR(0.75f)

加載因子可設(shè)為>1,即永不會(huì)擴(kuò)容,(犧牲性能節(jié)省內(nèi)存)

3.1.3 size

Map中現(xiàn)在有的鍵值對數(shù)量,每put一個(gè)entry,++size

The number of key-value mappings contained in this map.

3.1.4 threshold

數(shù)組擴(kuò)容閾值。

即:HashMap數(shù)組總?cè)萘?* 加載因子(16 * 0.75 = 12)。當(dāng)前size大于或等于該值時(shí)會(huì)執(zhí)行擴(kuò)容resize()。擴(kuò)容的容量為當(dāng)前 HashMap 總?cè)萘康膬杀?。比如,?dāng)前 HashMap 的總?cè)萘繛?16 ,那么擴(kuò)容之后為 32

3.2 關(guān)于樹

  1. 樹形化閾值TREEIFY_THRESHOLD。當(dāng)鏈表的節(jié)點(diǎn)個(gè)數(shù)大于等于這個(gè)值時(shí),會(huì)將鏈表轉(zhuǎn)化為紅黑樹。

  2. 解除樹形化閾值UNTREEIFY_THRESHOLD 。當(dāng)鏈表的節(jié)點(diǎn)個(gè)數(shù)小于等于這個(gè)值時(shí),會(huì)將紅黑樹轉(zhuǎn)換成普通的鏈表

  3. MIN_TREEIFY_CAPACITY 樹形化閾值的第二條件。當(dāng)數(shù)組的長度小于這個(gè)值時(shí),就算樹形化閾達(dá)標(biāo),鏈表也不會(huì)轉(zhuǎn)化為紅黑樹,而是優(yōu)先擴(kuò)容數(shù)組resize()

4. hashCode

4.1 hashCode()

獲取哈希碼,objecthashCode()方法是個(gè)本地方法,是由C實(shí)現(xiàn)的。

理論上hashCode是一個(gè)int值,這個(gè)int值范圍在-2147483648和2147483648之間,如果直接拿這個(gè)hashCode作為HashMap中數(shù)組的下標(biāo)來訪問的話,正常情況下是不會(huì)出現(xiàn)hash碰撞的。
但是這樣的話會(huì)導(dǎo)致這個(gè)HashMap的數(shù)組長度比較長,長度大概為40億,內(nèi)存肯定是放不下的,所以這個(gè)時(shí)候需要把這個(gè)hashCode對數(shù)組長度取余,用得到的余數(shù)來訪問數(shù)組下標(biāo)。

4.2 計(jì)算索引的過程

  1. 調(diào)用hashCode()得到一串?dāng)?shù)字超長的數(shù)字h
  2. 將h右移16位,與h按位異或(^),得到hash(擾動(dòng)函數(shù))
  3. 將(n-1)與hash按位與(&)
    此處n-1指 (1111)_2(即15),因?yàn)閿?shù)組默認(rèn)長度16(n)
  4. 得到下標(biāo)

你知道hash的實(shí)現(xiàn)嗎?為什么要這樣實(shí)現(xiàn)?

在Java 1.8的實(shí)現(xiàn)中,是通過把計(jì)算得到的hashCode()的右移16位,異或低16位實(shí)現(xiàn)的:(h = k.hashCode()) ^ (h >>> 16),這里得到的hash(n-1)按位與之后就是索引。

主要是從速度、功效、質(zhì)量來考慮的,

  1. 這么做保證高低bit都參與到hash的計(jì)算中,保留了一部分高位的信息(加大了低位隨機(jī)性,減少了沖突)(如果不移位,高位信息就用不上)
  2. 同時(shí)不會(huì)有太大的開銷。

高低位異或,避免高位不同,低位相同的兩個(gè)hashCode值 產(chǎn)生碰撞。

Java1.7中 hash的實(shí)現(xiàn)

static int hash(int h) {
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

/**
     * Returns index for hash code h.
     */
static int indexFor(int h, int length) {
    return h & (length-1);
    //這里為什么用&位運(yùn)算??(下文)
}

一個(gè)數(shù)對2^n取模 == 一個(gè)數(shù)和(2^n – 1)做按位與運(yùn)算 。

X % 2^n == X & (2^n – 1)
//2^n表示2的n次方

假設(shè)n為3,則2^3 = 8,表示成2進(jìn)制就是1000。2^3 -1 = 7 ,即0111。

假設(shè)x=18,也就是 0001 0010,一起看下結(jié)果:
對 2的n次方 取模: 18 % 8 = 2
對 2的n次方-1 按位與: 0001 0010 & 0111 = 0000 0010 = 2

4.3 HashCode在equals方法中的作用

為什么重寫 equals 時(shí)必須重寫 hashCode 方法?

hashCode()的默認(rèn)行為是對堆上的對象產(chǎn)生獨(dú)特值。如果沒有重寫 hashCode(),則該 class 的兩個(gè)對象無論如何都不會(huì)相等(即使這兩個(gè)對象指向相同的數(shù)據(jù))

hashCode 方法的常規(guī)協(xié)定聲明 相等對象必須具有相等的哈希碼 。

equals()既然已能對比的功能了,為什么還要hashCode()呢?因?yàn)橹貙懙膃quals()里一般比較的比較全面比較復(fù)雜,這樣效率就比較低,而利用hashCode()進(jìn)行對比,則只要生成一個(gè)hash值進(jìn)行比較就可以了,效率很高。

hashcode 只是用來縮小查找成本

hashCode()既然效率這么高為什么還要equals()呢?因?yàn)閔ashCode()并不是完全可靠,有時(shí)候不同的對象他們生成的hashcode也會(huì)一樣(生成hash值得公式可能存在的問題),所以hashCode()只能說是大部分時(shí)候可靠,并不是絕對可靠,

5. RESIZE(擴(kuò)容)

5.1 為什么擴(kuò)容

  1. hashMap是懶加載的,第一次用之前,Table都是空的,第一次用需要擴(kuò)容

  2. 當(dāng)容量(size屬性 * )達(dá)到閾值threshold減少hash沖突

    • size屬性就是指所有的<k,v>數(shù)量,不管在不在同一個(gè)鏈表內(nèi)都算;下文是putValue()源碼,這個(gè)++size是在所有if else之外的,所以不管新節(jié)點(diǎn)放到哪size都會(huì)增加。

      if (++size > threshold)
          resize();
      

5.2 擴(kuò)容到多少?

  • 自動(dòng)擴(kuò)容的容量為當(dāng)前 HashMap 總?cè)萘康膬杀叮?/p>

  • 如果是指定了容量,tableSizeFor函數(shù)會(huì)根據(jù)傳入的參數(shù),尋找距離最近的2的N次方;傳75,容量就是128.

    http://www.itdecent.cn/p/f86191afd918 (tableSizeFor函數(shù)源碼解析)

5.3 為什么擴(kuò)容是2倍?

  1. 容量是2的次冪,結(jié)合計(jì)算索引的位運(yùn)算,可以比較大程度分散元素,散列效果更好

  2. 計(jì)算索引會(huì)更高效(尤其是擴(kuò)容時(shí),用容量n和hash按位與很方便高效,(實(shí)際上是查看最高位是0/1))

    計(jì)算索引本質(zhì)是取模運(yùn)算,當(dāng)容量是2的次冪時(shí),取??梢赞D(zhuǎn)化為快速的位運(yùn)算。

HashMap為了存取高效,要盡量較少碰撞,就是要盡量把數(shù)據(jù)分配均勻,每個(gè)鏈表長度大致相同,這個(gè)實(shí)現(xiàn)就在把數(shù)據(jù)存到哪個(gè)鏈表中的算法;

這個(gè)算法實(shí)際就是取模,hash%length。 但是,大家都知道這種運(yùn)算不如位移運(yùn)算快

Jdk1.8中,用的是hash&(length-1),如果容量是2的n次方,在計(jì)算索引時(shí),(length - 1)的二進(jìn)制形式上每一位都是1,這樣與hash進(jìn)行按位與會(huì)更大程度的分散元素,

例如長度為8時(shí)候,3&(8-1)=3 2&(8-1)=2 ,不同位置上,不碰撞。 而長度為5的時(shí)候,3&(5-1)=0 2&(5-1)=0,都在0上,出現(xiàn)碰撞了

5.3 什么時(shí)候擴(kuò)容

A:兩個(gè)時(shí)候

  1. HashMap實(shí)行了懶加載, 新建HashMap時(shí)不會(huì)對table進(jìn)行賦值, 而是到第一次插入時(shí), 進(jìn)行resize時(shí)構(gòu)建table;當(dāng)新建時(shí), 如果沒有指定HashMap.table的初始長度, 就用默認(rèn)值16, 否則就是指定的值; 然后不管是第一次構(gòu)建還是后續(xù)擴(kuò)容, threshold = capacity * loadFactor(比如往HashMap里面放了一對值,threshold就會(huì)更新)
  2. 當(dāng)HashMap.size 大于 threshold時(shí), 會(huì)進(jìn)行resize;threshold的值:

5.4 loadFactor為什么是0.75

Node[] table,即哈希桶數(shù)組,哈希桶數(shù)組table的長度length大小必須為2的n次方

0.75 * 2^n 得到的都是整數(shù)。

5.5 HashCode的計(jì)算在擴(kuò)容上的好處

把bucket擴(kuò)充為2倍,之后重新計(jì)算index,把節(jié)點(diǎn)再放到新的bucket中

hashCode是很長的一串?dāng)?shù)字,<font color = orange>(換成二進(jìn)制,此元素的位置就是后四位組成的 (數(shù)組的長度為16,即4位))</font>

擴(kuò)容 = 把作為索引的數(shù)字范圍往前一個(gè)

eg.

<font color = gray>1111 1111 1111 1111 0000 1111 0001</font> 1111 (原索引是后面四個(gè),索引是15)

擴(kuò)容后:
<font color = gray>1111 1111 1111 1111 0000 1111 000</font>1 1111 (新的索引多了一位)(多出來這個(gè),或1或0 隨機(jī),完全看hash)

因此,我們在擴(kuò)充HashMap的時(shí)候,不需要重新計(jì)算hash,只需要看看原來的hash值新增的那個(gè)bit是1還是0就好了,是0的話索引沒變,是1的話索引變成“原索引+oldCap”。

if ((e.hash & oldCap) == 0) {// oldCap == 1000 按位與就是看第一位是不是1
    if (loTail == null)
        loHead = e;
    else
        loTail.next = e;
    loTail = e;
}
else {
    if (hiTail == null)
        hiHead = e;
    else
        hiTail.next = e;
    hiTail = e;
}

這個(gè)設(shè)計(jì)確實(shí)非常的巧妙,既省去了重新計(jì)算hash值的時(shí)間,而且同時(shí),由于新增的1bit是0還是1可以認(rèn)為是隨機(jī)的(hashCode里被作為索引的數(shù)往前走了一個(gè),走的這個(gè)可能是0,也可能是1),因此resize的過程,均勻的把之前的沖突的節(jié)點(diǎn)分散到新的bucket了。

6. HashMap使用

6.1 遍歷

用Iterator有兩種方式,分別把迭代器放到entry和keyset上,第一種更推薦,因?yàn)椴恍枰?code>get(key)

Iterator<Map.Entry<String, Integer>> entryIterator = map.entrySet().iterator();
while (entryIterator.hasNext()) {
    Map.Entry<String, Integer> next = entryIterator.next();
    System.out.println("key=" + next.getKey() + " value=" + next.getValue());
}
        
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()){
    String key = iterator.next();
    System.out.println("key=" + key + " value=" + map.get(key));
}

7. 常見問題

7.1 樹形化閾值為何是8?

  1. 如果選擇6和8(如果鏈表小于等于6樹還原轉(zhuǎn)為鏈表,大于等于8轉(zhuǎn)為樹),中間有個(gè)差值7可以有效防止鏈表和樹頻繁轉(zhuǎn)換。假設(shè)一下,如果設(shè)計(jì)成鏈表個(gè)數(shù)超過8則鏈表轉(zhuǎn)換成樹結(jié)構(gòu),鏈表個(gè)數(shù)小于8則樹結(jié)構(gòu)轉(zhuǎn)換成鏈表,如果一個(gè)HashMap不停的插入、刪除元素,鏈表個(gè)數(shù)在8左右徘徊,就會(huì)頻繁的發(fā)生樹轉(zhuǎn)鏈表、鏈表轉(zhuǎn)樹,效率會(huì)很低。
  2. 還有一點(diǎn)重要的就是由于treeNodes的大小大約是常規(guī)節(jié)點(diǎn)的兩倍,因此我們僅在容器包含足夠的節(jié)點(diǎn)以保證使用時(shí)才使用它們,當(dāng)它們變得太?。ㄓ捎谝瞥蛘{(diào)整大?。r(shí),它們會(huì)被轉(zhuǎn)換回普通的node節(jié)點(diǎn),容器中節(jié)點(diǎn)分布在hash桶中的頻率遵循泊松分布,桶的長度超過8的概率非常非常小。所以作者應(yīng)該是根據(jù)概率統(tǒng)計(jì)而選擇了8作為閥值

7.2 key的選取

7.2.1 為什么Integer String等包裝類適合作為key

可減少哈希碰撞

  1. 因?yàn)?String、Integer 等包裝類是 final 類型的,具有不可變性,不可變性保證了計(jì)算 hashCode() 后鍵值的唯一性和緩存特性,不會(huì)出現(xiàn)放入和獲取時(shí)哈希碼不同的情況
  2. 包裝類已經(jīng)重寫了 equals() 和 hashCode() 方法,而這些方法是在存取時(shí)都會(huì)用到的。
    讀取哈希值高效,此外官方實(shí)現(xiàn)的 equals() 和 hashCode() 都是嚴(yán)格遵守相關(guān)規(guī)范的,不會(huì)出現(xiàn)錯(cuò)誤。

7.2.2 Null可以做key嗎

可以,null的索引被設(shè)置為0,也就是Table[0]位置
在JDK7中,調(diào)用了putForNullKey()方法,處理空值

JDK8中,則修改了hash函數(shù),在hash函數(shù)中直接把key==null的元素hash值設(shè)為0,

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//如果key為Null,直接把hash設(shè)為0

再通過計(jì)算索引的步驟

//(JDK11,函數(shù):putVal())
if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
//索引:i = (n - 1) & hash

得到索引為0;

7.2.3 如何設(shè)計(jì)一個(gè)class作為key

  1. 重寫equals方法,equals方法與hashCode()的關(guān)系保證正確(hashCode相同不一定equals,equals一定hashCode相同)
  2. 讓這個(gè)類不可變
    1. 添加final修飾符,保證類不被繼承
    2. 保證所有成員變量必須私有,并且加上final修飾
    3. 不提供改變成員變量的方法,包括setter
    4. 通過構(gòu)造器初始化所有成員,進(jìn)行深拷貝(deep copy) ,傳入的構(gòu)造器參數(shù)也復(fù)制一份,而不是直接用引用
    5. 在getter方法中,不要直接返回對象本身,而是克隆對象,并返回對象的拷貝

7.3 為什么使用數(shù)組+鏈表的結(jié)構(gòu)

  1. 數(shù)組根據(jù)索引取值效率高,用來根據(jù)key確定桶的位置
  2. 鏈表用來解決hash沖突,索引值一樣時(shí),就在鏈表上增加一個(gè)節(jié)點(diǎn)。

7.3.1 為什么不用linkedList或者ArrayList代替數(shù)組

  1. 因?yàn)橛兴饕瑪?shù)組取值比LinkedList快;
  2. 數(shù)組是基本的數(shù)據(jù)結(jié)構(gòu),操作更方便(擴(kuò)容機(jī)制可以自定義,ArrayList擴(kuò)容是變成1.5倍容量)

7.4 hashmap中g(shù)et元素的過程?

對key的hashCode()做hash運(yùn)算,計(jì)算index; 如果在bucket里的第一個(gè)節(jié)點(diǎn)里直接命中,則直接返回; 如果有沖突,則通過key.equals(k)去查找對應(yīng)的Entry;

  • 若為樹,則在樹中通過key.equals(k)查找,O(logn);
  • 若為鏈表,則在鏈表中通過key.equals(k)查找,O(n)。

7.5 hashmap中put元素的過程?

調(diào)用putValue:

  1. 查看當(dāng)前Table是否為空,為空就resize;
  2. 查看對應(yīng)索引處是否為空,是就直接newNode()放到Table[i]

7.5 說說String中hashcode的實(shí)現(xiàn)?

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

是以31為權(quán),每一位為字符的ASCII值進(jìn)行運(yùn)算,用int的自然溢出來等效取模。

假設(shè)String是ABC,

((A + 0*31) * 31  + 'B' )*31 + 'C' 

7.5.1 為什么每次乘31?

  1. 31是個(gè)比較大的質(zhì)數(shù),適合用于hash
  2. 31 == 2^5 - 1,很適合改成位運(yùn)算,i* 31 == (i << 5) - i

7.6 為什么不直接用紅黑樹

  1. 空間資源考慮:紅黑樹節(jié)點(diǎn)大小比鏈表節(jié)點(diǎn)大,占空間更多
  2. 時(shí)間資源:雖然查詢快,但是需要左右旋轉(zhuǎn),調(diào)整顏色等保持紅黑樹的性質(zhì),在節(jié)點(diǎn)比較少的情況下,節(jié)省下來的查詢時(shí)間開銷并不值。

7.7 HashMap在并發(fā)時(shí)有什么問題?1.8還有嗎?如何解決

  1. 多線程擴(kuò)容后,取元素時(shí)的死循環(huán)(1.8解決)

  2. 多線程put可能導(dǎo)致元素丟失(未解決)

    元素為什么會(huì)丟失?

    1. 元素put時(shí)是一種“先查后改“的機(jī)制,即先計(jì)算得到索引,然后再往桶里面放;查詢可以同步進(jìn)行(假設(shè)有兩個(gè)索引相同的元素需要put,查詢出前一個(gè)節(jié)點(diǎn)都是A,那么插入時(shí),后插入的C可能就會(huì)覆蓋先插入的B)先插入的B就丟失了。
    2. put函數(shù)中有一句++size表示HashMap當(dāng)前的Entry數(shù)量,但該操作不是原子的,源碼設(shè)計(jì)時(shí)就沒有考慮其并發(fā)安全性。在多線程執(zhí)行這句++size時(shí),結(jié)果很可能出錯(cuò),這導(dǎo)致元素個(gè)數(shù)是錯(cuò)的
  3. put非null元素get出來卻是null(未解決)

可以使用ConcurrentHashmap,Hashtable等線程安全等集合類。

7.9 map中的對象能否修改

Divenier總結(jié):

要看作為key的元素的類如何實(shí)現(xiàn)的,如果修改的部分導(dǎo)致其hashcode變化,則修改后不能get()到;

如修改部分對hashcode無影響,則可以修改。

因?yàn)?key 更新后 hashCode 也更新了,(這里是因?yàn)橹貙懥?hashcode 的原因)而 HashMap 里面的對象是我們原來哈希值的對象,在 get 時(shí)由于哈希值已經(jīng)變了,原來的對象不會(huì)被索引到了,所以結(jié)果為 null

因此當(dāng)把對象放到 HashMap 后就不要嘗試對 key 進(jìn)行修改操作,謹(jǐn)防出現(xiàn)哈希值變化或者 equals 比較不等的情況導(dǎo)致無法索引。

7.10 解決Hash沖突有幾個(gè)辦法?HashMap用的是什么辦法?

開放定址法、鏈地址法(拉鏈法)、再Hash法

  • HashMap用的是拉鏈法。

  • ThreadLocalMap是開放定址法

之所以采用不同的方式主要是因?yàn)?在 ThreadLocalMap中的散列值分散得十分均勻,很少會(huì)出現(xiàn)沖突。并且 ThreadLocalMap經(jīng)常需要清除無用的對象,使用純數(shù)組更加方便。

8. 并發(fā)安全

8.1 HashMap和HashTable

  • 都實(shí)現(xiàn)了map接口,HashMap繼承自AbstractMap ,實(shí)現(xiàn)此接口比較簡單, HashTable繼承自Dictionary類(已廢棄),都是鍵值對存儲(chǔ)方式
  • HashMap可以有Null鍵(位置是0),HashTable則不可以(直接返回 NullPointerException)
  • HashMap線程不安全(非synchronized) Table安全(幾乎所有的 public方法都加了synchronized,所以線程安全)

在 Collections 類中存在一個(gè)靜態(tài)方法:synchronizedMap(),該方法創(chuàng)建了一個(gè)線程安全的 Map 對象,并把它作為一個(gè)封裝的對象來返回。

部分參考資料:

https://tech.meituan.com/2016/06/24/java-hashmap.html

https://zhuanlan.zhihu.com/p/76735726

https://zhuanlan.zhihu.com/p/111501405

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.HashMap底層原理 數(shù)據(jù)結(jié)構(gòu)底層使用哈希表(數(shù)組+鏈表進(jìn)行存儲(chǔ)),在jdk1.8后 當(dāng)鏈表長度大于等于8時(shí)...
    小甲說閱讀 430評論 0 0
  • 接口(interface)和抽象類(abstract class)的區(qū)別是什么? 一個(gè)類實(shí)現(xiàn)(implemens)...
    BySjm閱讀 801評論 0 5
  • jdk1.8對hashmap的簡單介紹 基于哈希表實(shí)現(xiàn)的Map接口。此實(shí)現(xiàn)提供了所有可選映射操作,并允許空值(va...
    入門小站閱讀 308評論 0 1
  • 1:HashMap 的數(shù)據(jù)結(jié)構(gòu)? A:哈希表結(jié)構(gòu)(鏈表散列:數(shù)組+鏈表)實(shí)現(xiàn),結(jié)合數(shù)組和鏈表的優(yōu)點(diǎn)。當(dāng)鏈表長度超過...
    碼農(nóng)_夏摯閱讀 165評論 0 0
  • 久違的晴天,家長會(huì)。 家長大會(huì)開好到教室時(shí),離放學(xué)已經(jīng)沒多少時(shí)間了。班主任說已經(jīng)安排了三個(gè)家長分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,866評論 16 22

友情鏈接更多精彩內(nèi)容