HashMap的底層原理

步江伍德發表於2021-05-15
package com.programme.demo01;

import java.util.HashSet;
import java.util.List;

/**
 * @program: spring
 * @description: ${description}
 * @author: Mr.zw
 * @create: 2021-05-15 10:26
 **/
public class Demo01 {
    public static void main(String[] args) {
        HashSet<Integer> set = new HashSet<>();
        set.add(3);
        set.add(2);
        set.add(4);
        set.add(5);
        set.add(16);
        set.add(17);
        set.add(7);
        set.add(1);
        set.add(6);
        set.add(9);
        set.add(8);
        set.add(10);
        System.out.println(set);
       //輸出: [16, 17, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    }
}

  新增元素

package com.programme.demo01;

import java.util.HashSet;
import java.util.List;

/**
 * @program: spring
 * @description: ${description}
 * @author: Mr.zw
 * @create: 2021-05-15 10:26
 **/
public class Demo01 {
    public static void main(String[] args) {
        HashSet<Integer> set = new HashSet<>();
        set.add(3);
        set.add(2);
        set.add(4);
        set.add(5);
        set.add(16);
        set.add(17);
        set.add(7);
        set.add(1);
        set.add(6);
        set.add(9);
        set.add(8);
        set.add(10);
        set.add(11);
        set.add(12);
        set.add(13);
        System.out.println(set);
        //輸出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16, 17]
    }
}

  這裡我們的16,17到了後面去了,這涉及到雜湊表的底層,set集合的底層其實也是hashmap

 

 雜湊桶也是有索引的,雜湊桶的初始容量是十六

 

 1<<4;是1左移四位,也就是1乘以2的4次方,>>表示右移除以。

 

 

假設這裡有一個元素要進來想往hashmap裡面存,先是用hashcode.equals判斷是否有值然後通過put存放進去

 

 通過計算雜湊值來放入tab中也就等於我們的陣列中

 

 通過這個方法計算雜湊值擾亂函式低十六位異或高十六位 這個操作叫做擾亂函式

 

 將計算的雜湊值右移16位,讓雜湊值儘量分散開,查詢map的時候儘量快一點,所以也雜湊表也叫雜湊表

異或相同為0不同為1

 

 

 

 計算出來的雜湊值這麼大怎麼將元素存入一個索引為0-15的雜湊表去

 

 長度必須是二的幾次方

 

 

 如果我們自定義的容量不是二的幾次方也會強轉為2的次方

 

 

 這麼做的目的就是為了計算存放索引

預設長度16的的2進位制是10000 看原始碼的操作

 

 

 這裡將10000-1得到1111 再進行與操作 得到0101也就是5索引 這種操作叫做路由定址

 

 我們將元素存到5索引

 

 假設這個又來個一個元素 通過計算也存到了5索引 就發生了雜湊碰撞(雜湊衝突)第一個存進去的元素後面有個拉鍊 將他們拉起來 1.7是頭插法1.8是尾插法

 

 如果我們的陣列存滿了就會擴容,比如16擴容成了32,不能讓後面16位空著,要儘量讓它雜湊,1.7會出現環形連結串列也就是著名的約瑟夫環問題。

 

 在 Java 1.8 中,如果連結串列長度到8,陣列容量到64,連結串列會樹化;刪除元素紅黑樹長度小於6,紅黑樹退化為連結串列;(這裡的陣列容量指的是陣列長度,不是64個元素)

 

 

何時擴容 陣列中 長度*載入因子(0.75)個位置上都有元素,再存會擴容 

 

 16乘以0.75等於12

package com.programme.demo01;

import com.sun.org.apache.xpath.internal.operations.Equals;

import java.util.HashSet;
import java.util.List;

/**
 * @program: spring
 * @description: ${description}
 * @author: Mr.zw
 * @create: 2021-05-15 10:26
 **/
public class Demo01 {
    public static void main(String[] args) {
        HashSet<Integer> set = new HashSet<>();

        set.add(3);//1
        set.add(2);//2
        set.add(4);//3
        set.add(5);//4
        set.add(16);//5
        set.add(17);//6
        set.add(7);//7
        set.add(1);//8
        set.add(6);//9
        set.add(9);//10
        set.add(8);//11
        set.add(10);//12
        set.add(11);//13
        //輸出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16, 17]
        System.out.println(set);
    }
}

  

package com.programme.demo01;

import com.sun.org.apache.xpath.internal.operations.Equals;

import java.util.HashSet;


/**
 * @program: spring
 * @description: ${description}
 * @author: Mr.zw
 * @create: 2021-05-15 10:26
 **/
public class Demo01 {
    public static void main(String[] args) {
        HashSet<Integer> set = new HashSet<>();

        set.add(3);//1
        set.add(2);//2
        set.add(4);//3
        set.add(5);//4
        set.add(16);//5
        set.add(17);//6
        set.add(7);//7
        set.add(1);//8
        set.add(6);//9
        set.add(9);//10
        set.add(8);//11
        set.add(10);//12
        //輸出:[16, 17, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        System.out.println(set);
    }
}

  

package com.programme.demo01;

import com.sun.org.apache.xpath.internal.operations.Equals;

import java.util.HashSet;


/**
 * @program: spring
 * @description: ${description}
 * @author: Mr.zw
 * @create: 2021-05-15 10:26
 **/
public class Demo01 {
    public static void main(String[] args) {
        HashSet<Integer> set = new HashSet<>();

        set.add(3);//1
        set.add(2);//2
        set.add(4);//3
        set.add(5);//4
        set.add(16);//5
        set.add(17);//6
        set.add(0);//7
        set.add(1);//8
        set.add(6);//9
        set.add(9);//10
        set.add(8);//11
        set.add(10);//12 16乘以0.75=13 未擴容
        //輸出:[16, 17, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        //16計算索引是0 17計算索引是1   16和0在一個鏈上 17和1在一個鏈上
        System.out.println(set);
    }
}

  擴容一下

package com.programme.demo01;

import com.sun.org.apache.xpath.internal.operations.Equals;

import java.util.HashSet;


/**
 * @program: spring
 * @description: ${description}
 * @author: Mr.zw
 * @create: 2021-05-15 10:26
 **/
public class Demo01 {
    public static void main(String[] args) {
        HashSet<Integer> set = new HashSet<>();

        set.add(3);//1
        set.add(2);//2
        set.add(4);//3
        set.add(5);//4
        set.add(16);//5
        set.add(17);//6
        set.add(0);//7
        set.add(1);//8
        set.add(6);//9
        set.add(9);//10
        set.add(8);//11
        set.add(10);//12
        set.add(11);//13 已擴容
        //輸出:[0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 16, 17] 16 17被分到擴容的位置上去了
        System.out.println(set);
    }
}

  在擴容的時候會將連結串列分成低位鏈和高位鏈,擴容時低位鏈不變,高位鏈分配是原索引加原長度(下圖原索引為1原長度為16,擴容後分配到17索引)

 

 

 如果在擴容的時候這個連結串列已經樹化了,樹裡面也存放了計算上一個元素的地址值,雖然它是一棵樹,但是它裡面也有連結串列所用到的地址值

 

 

 

 為什麼連結串列長度到8了進化紅黑樹,不是7或者9呢,這就涉及到概率論:泊松分佈

這個連結串列到8的概率其實是非常低的 ,Java是優先擴容的,而不是優先拉鍊,雜湊碰撞的概率其實是很低的。

 

 

 

 

全圖

 

相關文章