• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • 詳細分析jdk1.7的HashMap源碼

    標簽: 面試專欄  java

    先看一段代碼

    public static void main(String[] args) {
           Map<String,Object> map=new HashMap<>();
           map.put("key=1","value=100");
           Object put = map.put("key=1", "value=200");
           System.out.println(put);
       }
    

    結果

    //value=100
    

    我們知道map中一個key只能對應一個value,我們再put這個key,會把原來的value返回回來。

    為什么jdk1.7 HashMap 的實現原理:數組+鏈表

    我們要向一個數組中存入一個值,我們必須知道你要存的下標
    jdk是如何實現的呢

        public V put(K key, V value) {
            if (table == EMPTY_TABLE) {
                inflateTable(threshold);
            }
            if (key == null)
                return putForNullKey(value);
            int hash = hash(key);
            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;
        }
    

    可以看到它通過hash()這個方法計算了一個hash值,再通過indexFor()這個方法根據hash值和數組長度算出索引值i
    在這里插入圖片描述
    采用鏈表的結構很大程度上解決了hash沖突問題。
    為什么第二個在上面,這是由于jdk1.7 采用了頭插的方式,這個下面再敘述

    看一下這個indexFor方法

        static int indexFor(int h, int length) {
            // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
            return h & (length-1);
        }
    

    HashMap的初始容量和擴容都是以2的次方來進行的,那么length-1換算成二進制的話肯定所有位都為1,就比如數組長度為8,2的3次方為8(1000),length-1的二進制表示就是111, 而按位與計算的原則是兩位同時為“1”,結果才為“1”,否則為“0”。假設計算出來的hash值為135
    135(10000111)&7(111)=7,現在,數組下標為7,所以h& (length-1)運算從數值上來講其實等價于對length取模,也就是h%length。

    如果不滿足前提條件“HashMap的初始容量和擴容都是以2的次方來進行的”,會發生什么問題呢?

    假設當前table的length是15,二進制表示為1111,那么length-1就是1110,此時有兩個hash值為8和9的key需要計算索引值,計算過程如下:

    //8的二進制表示:1000
    //8&(length-1)= 1000 & 1110 = 1000,索引值即為8;
    
    //9的二進制表示:1001
    //9&(length-1)= 1001 & 1110 = 1000,索引值也為8;
    

    這樣一來就產生了相同的索引值,也就是說兩個hash值為8和9的key會定位到數組中的同一個位置上形成鏈表,這就產生了碰撞
    同時,我們也可以發現,當數組長度為15的時候,hash值會與length-1(1110)進行按位與,那么最后一位永遠是0,而0001,0011,0101,1001,1011,0111,1101這幾個位置永遠都不能存放元素了,會造成嚴重的空間浪費,更糟的是這種情況下,數組可以使用的位置比數組長度小了很多,這意味著進一步增加了碰撞的幾率,減慢了查詢的效率。
    因此可以看出,只有當數組長度為2的n次方時,不同的key計算得出的index索引相同的幾率才會較小,數據在數組上分布也比較均勻,碰撞的幾率也小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了。
    此外,位運算快于十進制運算,hashmap擴容也是按位擴容,這樣同時也提高了運算效

    HashMap的構造函數

        static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //數組默認的容量為16
        static final int MAXIMUM_CAPACITY = 1 << 30;//最大
        static final float DEFAULT_LOAD_FACTOR = 0.75f;//擴容因子
        static final Entry<?,?>[] EMPTY_TABLE = {};
        transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//Entry數組
        transient int size;
        int threshold;//閾值
        final float loadFactor;
        transient int modCount;
        static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
    	/*
    	*initialCapacity傳進來的數組容量,loadFactor擴容因子
    	*先對這兩個值進行合法判斷
    	*threshold = initialCapacity;把容量賦值給了一個閾值?
    	*/
        public HashMap(int initialCapacity, float loadFactor) {
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal initial capacity: " +
                                                   initialCapacity);
            if (initialCapacity > MAXIMUM_CAPACITY)
                initialCapacity = MAXIMUM_CAPACITY;
            if (loadFactor <= 0 || Float.isNaN(loadFactor))
                throw new IllegalArgumentException("Illegal load factor: " +
                                                   loadFactor);
    
            this.loadFactor = loadFactor;
            threshold = initialCapacity;
            init();
        }
        public HashMap(int initialCapacity) {
            this(initialCapacity, DEFAULT_LOAD_FACTOR);
        }
        public HashMap() {
            this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
        }
        public HashMap(Map<? extends K, ? extends V> m) {
            this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                          DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
            inflateTable(threshold);
    
            putAllForCreate(m);
        }
    

    put方法

    	/*
    	*	首先判斷table數組是否為空,如果是空的進行初始化	inflateTable()
    	*/
        public V put(K key, V value) {
            if (table == EMPTY_TABLE) {
                inflateTable(threshold);
            }
            if (key == null)
                return putForNullKey(value);
            int hash = hash(key);
            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;
        }
    
    	/*
    	*	這個方法主要是對table進行初始化,table = new Entry[capacity];
    	*	它為什么不直接把我們傳進來的容量作為數組大小呢,
    	*	這個問題與上面容量和擴容都是2的次方數一樣
    	*	int capacity = roundUpToPowerOf2(toSize);找到大于這toSize的最小2的次方數
    	*	例如	7-----》8		9---------》16
    	*	那么它是如何實現的?
    	*/
    private void inflateTable(int toSize) {
            // Find a power of 2 >= toSize
            int capacity = roundUpToPowerOf2(toSize);
    
            threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
            table = new Entry[capacity];
            initHashSeedAsNeeded(capacity);
        }
    
    	/*
    	*	如果number》=MAXIMUM_CAPACITY 就等于MAXIMUM_CAPACITY   否則進入 (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1
    	*	如果	number > 1	進入	Integer.highestOneBit((number - 1) << 1)  否則等于1
    	*	這個方法的核心方法Integer.highestOneBit((number - 1) << 1)
    	*	highestOneBit下面講到是找到小于等于參數值的最小2的次方數
    	*	我們原本是想找到大于這toSize的最小2的次方數,這兩個方法看似完全相反
    	*	我們想找》10的最小2的次方數
    	*	1010 左移一位
    	*	0001 1010
    	*	highestOneBit(0001 1010)就是16
    	*	為什么要減一,如果傳入了一個2的次方數,比如8,不減一會返回16,需要減一再左移
    	*/
    private static int roundUpToPowerOf2(int number) {
            // assert number >= 0 : "number must be non-negative";
            return number >= MAXIMUM_CAPACITY
                    ? MAXIMUM_CAPACITY
                    : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
        }
    
    	/*
    	*	假設我想找到	小于等于10的最小2次方數
    	*	10------》1010
    	*	8-------》1000
    	*	
    	*	現在看這個方法,假設我傳進來了一個10( ... 0000 1010)
    	*	右移一位							( ... 0000 0101)  進行或運算,有一則為一
    	*	結果							( ... 0000 1111)	
    	*	右移兩位							( ... 0000 0011)  進行或運算
    	*	結果							( ... 0000 1111)	
    	*	。。。
    	*	最終i=		 (  ... 0000 1111)	
    	*  	i進行右移一位 ( 	... 0000 0111)進行相減
    	* 	最終的返回值為(... 0000 1000) 8
    	*/
    public static int highestOneBit(int i) {
            // HD, Figure 3-1
            i |= (i >>  1);
            i |= (i >>  2);
            i |= (i >>  4);
            i |= (i >>  8);
            i |= (i >> 16);
            return i - (i >>> 1);
        }
    
    版權聲明:本文為weixin_45007916原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/weixin_45007916/article/details/106823327

    智能推薦

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    猜你喜歡

    統計學習方法 - 樸素貝葉斯

    引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...

    styled-components —— React 中的 CSS 最佳實踐

    https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...

    基于TCP/IP的網絡聊天室用Java來實現

    基于TCP/IP的網絡聊天室實現 開發工具:eclipse 開發環境:jdk1.8 發送端 接收端 工具類 運行截圖...

    19.vue中封裝echarts組件

    19.vue中封裝echarts組件 1.效果圖 2.echarts組件 3.使用組件 按照組件格式整理好數據格式 傳入組件 home.vue 4.接口返回數據格式...

    劍指Offer39-調整數組順序使奇數位于偶數前面

    一開始想著用冒泡排序的方法來做,但是bug還是很多,后來看了評論區答案,發現直接空間換時間是最簡單的,而且和快排的寫法是類似的。...

    精品国产乱码久久久久久蜜桃不卡