1. 程式人生 > >jdk1.7原始碼之-hashMap原始碼解析

jdk1.7原始碼之-hashMap原始碼解析

背景:

筆者最近這幾天在思考,為什麼要學習設計模式,學些設計模式無非是提高自己的開發技能,但是通過這一段時間來看,其實我也學習了一些設計模式,但是都是一些demo,沒有具體的例子,學習起來不深刻,所以我感覺我可能要換一條路走,所以我現在想法是看一些原始碼的東西,一方面是因為自己大部分的原始碼其實沒有看過,另一方面原始碼中可能會涉及到一些編碼風格和設計模式的東西,我也可以學習。

使用jdk版本:1.7.0_80

先從最簡單的開始:

public static void main(String[] args) {
        Map map = new HashMap();
    }

然後走到了這個方法:

/**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

這個是什麼意思呢?我們看這個方法的註釋:構建一個空的HashMap,使用預設的初始空間16和 裝載因子0.75

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 是預設的初始空間
static final float DEFAULT_LOAD_FACTOR = 0.75f; 預設的裝載因子的大小,
具體這兩個是幹什麼的我們下面再看
/**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * 
@param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public HashMap(int initialCapacity, float loadFactor) { //如果初始空間小於0,丟擲異常 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //若是初始空間大於1 << 30,1左移30位:1073741824 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //這裡判斷裝載因子是否小於0,以及判斷是否非值,這裡額外說明一下 //Float.isNaN 有幾種情況 /** Float f1 =new Float(-1.0/0.0); //-Infinity (負無窮) Float f2 =new Float(0.0/0.0); // Infinity (正無窮) Float f3 =new Float(1.0/0.0); // NAN 只有這種呼叫isNaN 是true */ if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; //目前設定的值是容器大小的值,後面還有作用 threshold = initialCapacity; //這個init方法在hashMap中為空,但在LinkedHashMap中有重寫 init(); }

我看在new的時候,HashMap並沒有建立陣列和儲存,所以我思考可能是在put的時候進行陣列的初始化

所以我們來深究以下put方法

/**
* 這裡是HashMap的put方法,hashMap是由陣列和連結串列組成的
*/
public V put(K key, V value) {
//如果是空陣列的話,就初始化一個
//    static final Entry<?,?>[] EMPTY_TABLE = {};
        if (table == EMPTY_TABLE) {
//從上面那個步驟我們可以知道,threshold=16,這個步驟應該是初始化陣列
            inflateTable(threshold);
        }
//我們知道hashMap的key是可以存null的,這裡應該是對key為null的時候做的邏輯處理
        if (key == null)
            return putForNullKey(value); // ok 那這個方法我們一會再看
        int hash = hash(key);//hashMap的hash運算
        int i = indexFor(hash, table.length); //hash值與表長度 按位與計算
//根據運算得到的是陣列的索引,
//下面for迴圈中的e就是陣列中的一個索引,這個索引對應的值是一個連結串列
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
//如果hash值相同且key值也相等,則只是儲存value,然後返回老的值
//如果多個執行緒對hashMap操作,這裡不是執行緒安全的
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
//recordAccess hashMap沒有操作,LinkedHashMap有重寫
                e.recordAccess(this);
                return oldValue;
            }
        }
//    transient int modCount; 預設值為0,記錄hashMap結構修改的次數
        modCount++;
//增加陣列中的索引
        addEntry(hash, key, value, i);
        return null;
    }

 

然後 繼續看put方法中的  inflateTable 方法,它傳入的值是設定的預設空間大小 16

 

 /**
     * Inflates the table. 初始化陣列,一開始toSize傳入為16
     */
    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
//roundUpToPowerOf2 在下面
        int capacity = roundUpToPowerOf2(toSize);
//Math.min() 返回這兩個數的小的一個
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//初始化陣列,注意這裡取得是capacity
        table = new Entry[capacity];
//初始化hash掩碼值
        initHashSeedAsNeeded(capacity);
    }


private static int roundUpToPowerOf2(int number) {
        // assert number >= 0 : "number must be non-negative";
//判斷傳入的值是否大於最大值,看到若是number不大於1 直接返回1
        return number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
//number - 1 然後左移一位 相當於 (number - 1)*2
    }


// Integer.highestOneBit 這個是幹啥的呢,
//傳入一個int引數i,返回其二進位制最高位1的權值。(
//比如說 使用hashMap預設的構建方法,這裡傳入的是值也就是i是 30
public static int highestOneBit(int i) {
        // HD, Figure 3-1
        i |= (i >>  1);//經過第一步,i成為31
        i |= (i >>  2);//還是31
        i |= (i >>  4);//還是31
        i |= (i >>  8);//還是31
        i |= (i >> 16);//還是31
//使用i 無符號右移一位
        return i - (i >>> 1);
    }
//筆者嘗試使用 System.out.println(Integer.highestOneBit((16-1) <<1));  發現打印出來還是16


//求a 和 b 的最小值,使用預設建構函式的HashMap這裡傳入的是12,以及1 << 30 + 1
public static float min(float a, float b) {
        if (a != a) return a;   // a is NaN
        if ((a == 0.0f) && (b == 0.0f)
            && (Float.floatToIntBits(b) == negativeZeroFloatBits)) {
            return b;
        }
        return (a <= b) ? a : b;
    }


// 然後構建了16空間的陣列 table = new Entry[capacity];
//Entry中包括:
        final K key;
        V value;
        Entry<K,V> next; // 這裡應該是傳說中的陣列中的連結串列
        int hash;    //通過每個hash值判斷

//然後我們再看下這個在做什麼  initHashSeedAsNeeded(capacity);

/**
     * Initialize the hashing mask value. We defer initialization until we
     * really need it.
     * //翻譯:初始化雜湊掩碼值。我們推遲初始化直到我們真正的需要,傳入的引數就是 hashMAP的陣列大小,
     */
    final boolean initHashSeedAsNeeded(int capacity) {
// 這裡預設的hashSeed 為 0 ,currentAltHashing  為false
        boolean currentAltHashing = hashSeed != 0;
//sun.misc.VM.isBooted() 預設為 false 然後debug 出來 結果是 true
//useAltHashing  結果是 false
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
// currentAltHashing 、useAltHashing異或操作,得到結果false
        boolean switching = currentAltHashing ^ useAltHashing;
        if (switching) {
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }

//到這裡,我們可以看到初始化陣列已經完成

 

 inflateTable 方法分析完了,然後我們再看下 putForNullKey 方法:這個方法只有當key是null的時候才會進入

/**
     * Offloaded version of put for null keys
     */
    private V putForNullKey(V value) {
//取陣列的第一位,若是key為空,則把現在的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 標識 hashMap結構修改的次數
        modCount++;
//增加陣列節點
        addEntry(0, null, value, 0);
        return null;
    }


/**
     * Adds a new entry with the specified key, value and hash code to
     * the specified bucket.  It is the responsibility of this
     * method to resize the table if appropriate.
     *
     * Subclass overrides this to alter the behavior of put method.
     */
//這裡的hash,key,bucketIndex值在hashMap都是寫死的,value代表著你傳入的value值
//若是putForNullKey則,hash和bucketIndex是0
void addEntry(int hash, K key, V value, int bucketIndex) { /** The number of key-value mappings contained in this map. transient int size; map的數量 */ //開始的時候這個size是0,然後當size大於(需要擴容的數值),並且當前非空 if ((size >= threshold) && (null != table[bucketIndex])) { //重新擴大陣列大小 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } //擴建節點 createEntry(hash, key, value, bucketIndex); }

 

 /**
//建立
     * Like addEntry except that this version is used when creating entries
     * as part of Map construction or "pseudo-construction" (cloning,
     * deserialization).  This version needn't worry about resizing the table.
     *
     * Subclass overrides this to alter the behavior of HashMap(Map),
     * clone, and readObject.
     */
    void createEntry(int hash, K key, V value, int bucketIndex) {
//陣列連結串列
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

 

/**
     * Rehashes the contents of this map into a new array with a
     * larger capacity.  This method is called automatically when the
     * number of keys in this map reaches its threshold.
     *
     * If current capacity is MAXIMUM_CAPACITY, this method does not
     * resize the map, but sets threshold to Integer.MAX_VALUE.
     * This has the effect of preventing future calls.
     *
     * @param newCapacity the new capacity, MUST be a power of two;
     *        must be greater than current capacity unless current
     *        capacity is MAXIMUM_CAPACITY (in which case value
     *        is irrelevant).
     */
//這個方法很重要
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
//若是陣列的大小是最大值,則不處理
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
//新的陣列
        Entry[] newTable = new Entry[newCapacity];
//防止新的陣列節點
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;//覆蓋老的陣列
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);// 空間大小*0.75
    }

 

好了,大概hashMap就說到這裡吧,要是繼續說的話,我自己也不太清楚了,稍微看了下remove方法,裡面主要呼叫了removeEntryForKey方法,且裡面沒有對陣列大小的改變,也就是這個陣列只是增加的

下面是一張類圖,作為參考

先到這裡了,感覺沒有整體上的瞭解,希望以後更加努力!