java中的數據結構之第一節(HashMap部分源碼分析)

前言

在java業界,有一些問題在面試的時候能夠說基本上是必問的。好比:java中HashMap、HashTable、ConcurrentHashMap的區別,又或者是ArrayList 和 LinkedList 和 Vector 的區別。java

而這些問題,我總結一下,基本上都是java中的數據結構問題,咱們對這些數據結構的瞭解是很是淺薄的,好比說在1.8JDk版本先後的這些數據結構的變化是很是大的,可是咱們對這些卻依然不是很瞭解,不深刻進去,很難明白到底發生了什麼。那麼,接下來的一系列博客,都讓咱們來分析一下java中的數據結構吧。面試

今天,咱們先了解的是Hashmap。數據結構

ps : 我如今的jdk版本是1.8。函數

簡介

在JDK1.6,JDK1.7中,HashMap採用位桶+鏈表實現,即便用鏈表處理衝突,同一hash值的鏈表都存儲在一個鏈表裏。可是當位於一個桶中的元素較多,即hash值相等的元素較多時,經過key值依次查找的效率較低。this

而JDK1.8中,HashMap採用位桶+鏈表+紅黑樹實現,當鏈表長度超過閾值時,將鏈表轉換爲紅黑樹,這樣大大減小了查找時間。spa

主要參數

先來看HashMap裏面的一些參數: code

//默認初始大小是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;  
**************************************************************************************
//設置的最大容量是2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;    
**************************************************************************************
//設置的默認加載因子爲0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;   
**************************************************************************************
 //當某個桶節點數量大於8時,會轉換爲紅黑樹。
static final int TREEIFY_THRESHOLD = 8;        
**************************************************************************************
//當某個桶節點數量小於6時,會轉換爲鏈表,前提是它當前是紅黑樹結構。
static final int UNTREEIFY_THRESHOLD = 6; 
**************************************************************************************
//當整個hashMap中元素數量大於64時,也會進行轉爲紅黑樹結構。
static final int MIN_TREEIFY_CAPACITY = 64;
*************************************************************************************
//也是加載因子,只不過這個是變量。
final float loadFactor;
*************************************************************************************
 //臨界值,也就是元素數量達到臨界值時,會進行擴容。
int threshold;
複製代碼

PS:HashMap默認的加載因子是0.75,最大容量是16,所以能夠得出HashMap的默認容量是:0.75*16=12。cdn

加載因子是表示Hsah表中元素的填滿的程度.若:加載因子越大,填滿的元素越多,好處是,空間利用率高了,但:衝突的機會加大了.反之,加載因子越小,填滿的元素越少,好處是衝突的機會減少了,但空間浪費多了。blog

構造函數

看了HashMap的一些參數,如今咱們來了解一下HashMap的構造函數:ci

咱們看到在源碼中,HashMap有四個構造函數,在其中,HashMap(int initialCapacity)這個構造函數,實際上指向了另一個構造函數HashMap(int initialCapacity, float loadFactor)。咱們仍是把全部的構造函數都列出來吧。

構造函數1

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // 設置加載因子爲默認值0.75
}
複製代碼

構造函數2

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);   //指向構造函數3,且加載因子爲默認的0.75
}
複製代碼

構造函數3

public HashMap(int initialCapacity, float loadFactor) {
  //判斷初始加載容量是否小於0,如果,直接拋出異常
    if (initialCapacity < 0)          
        throw new IllegalArgumentException("Illegal initial capacity: " +  initialCapacity);
  //判斷初始加載容量是否大於最大容量,如果,設置初始加載容量爲最大容量
    if (initialCapacity > MAXIMUM_CAPACITY)  
        initialCapacity = MAXIMUM_CAPACITY;
  //判斷加載因子是否小於0或者判斷是否爲NaN,如果,拋出異常
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +loadFactor);
  //設置初始加載因子
    this.loadFactor = loadFactor;
  //對臨界值進行初始化,tableSizeFor(t)這個方法會返回大於t值的,且離其最近的2次冪,例如t爲29,則返回的值是32
    this.threshold = tableSizeFor(initialCapacity);
}
複製代碼

PS:NaN 實際上就是 Not a Number的簡稱。0.0f/0.0f的值就是NaN,從數學角度說,0/0就是一種未肯定。

構造函數4

public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;  //設置加載因子爲默認的0.75
    putMapEntries(m, false);
}

  final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    //獲取該map的實際長度
    int s = m.size();
    if (s > 0) {
        //判斷table是否初始化,若是沒有初始化
        if (table == null) { // pre-size
            //求出須要的容量,由於實際使用的長度=容量*0.75得來的,+1是由於小數相除,基本都不會是整數,容量大小不能爲小數的,後面轉換爲int,多餘的小數就要被丟掉,因此+1,例如,map實際長度22,22/0.75=29.3,所須要的容量確定爲30,有人會問若是剛恰好除得整數呢,除得整數的話,容量大小多1也沒什麼影響
            float ft = ((float)s / loadFactor) + 1.0F;
            //判斷該容量大小是否超出上限。
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            //對臨界值進行初始化,tableSizeFor(t)這個方法會返回大於t值的,且離其最近的2次冪,例如t爲29,則返回的值是32
            if (t > threshold)
                threshold = tableSizeFor(t);
        }
        //若是table已經初始化,且map的實際長度大於臨界值,則進行擴容操做,resize()就是擴容。
        else if (s > threshold)
            resize();
        //遍歷,把map中的數據轉到hashMap中。
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
複製代碼

HashMap的擴容方法很是複雜,等有機會了咱們再來分析,咱們只要明白在HashMap中的一些參數,就大概能明白HashMap的擴容方法的原理。

//當某個桶節點數量大於8時,會轉換爲紅黑樹。
static final int TREEIFY_THRESHOLD = 8;        
**************************************************************************************
//當某個桶節點數量小於6時,會轉換爲鏈表,前提是它當前是紅黑樹結構。
static final int UNTREEIFY_THRESHOLD = 6; 
**************************************************************************************
//當整個hashMap中元素數量大於64時,也會進行轉爲紅黑樹結構。
static final int MIN_TREEIFY_CAPACITY = 64;
複製代碼

JDK1.8中,HashMap採用位桶+鏈表+紅黑樹實現,當鏈表長度超過閾值時,將鏈表轉換爲紅黑樹,所以,在HashMap的擴容方法中,不單單是容量的變化,並且也有着數據結構本質上的變化。

總結

咱們發現,java中的HashMap是在與時俱進,變化一直都在。

還有一些關於HashMap的put和get方法,咱們在這裏沒有進行分析,可是因爲本人的技術還不夠,對於紅黑樹的不熟悉,所以不能深刻解析,但願之後在瞭解了紅黑樹之後,可以回過頭來更深刻的瞭解HashMap。

相關文章
相關標籤/搜索