1. 程式人生 > >HashMap原始碼分析·上

HashMap原始碼分析·上

感覺HashMap才是集大成者啊

繼承關係簡要圖

這裡寫圖片描述

HashMap類前註釋(搓翻譯)

挑重點看,挑重點翻譯~

一種基於散列表的Map介面實現。允許null值與null鍵。HashMapHashTable大致相同,區別在於前者是非同步且允許null。不保證順序,且順序可能會變。

如果hash函式足夠好,這種實現中的基礎操作(如getput)只需常量時間即可。選擇初始容量與載入因子非常重要,如果你非常在意Iterator的表現。

一個HashMap例項擁有兩個影響它的效能的因素:初始容量和載入因子。初始容量就是在hash表建立時桶的個數;載入因子是一種衡量雜湊表所允許的最大容量的引數,也就是capacity * 載入因子

,當超過此值時,雜湊表將進行rehash操作,也即容量將翻1倍。

通常來說,預設的載入因子0.75可以在時間消耗和空間消耗之間取得一個較好的平衡。過高,會減少空間消耗但會增加檢視消耗(表現在HashMap中的大部分操作,包括getput)。當設定它的初始容量時,為了減少rehash的次數,所預期的元素個數以及載入因子應當被考慮到。如果初始容量元素的個數除以載入因子結果要大,那麼將不會發生rehash操作。

如果要存很多元素,給一個充分大的容量給它,將會比“給個小容量然後讓其自動增長容量”這種方式更加高效。如果使用了過多的經過hashCode()處理後得到相同值的鍵,無論在任何雜湊表中,這都會表現得更慢。為了改善這種影響,當鍵是Comparable

是,將對他們進行比較。

什麼是對Map的結構性修改?新增或刪除某個鍵值對,修改不是。需要注意,此類不是執行緒同步的。

成員變數

// 預設起始容量-必須是2^n
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

// 最大容量,如果任何具有引數的建構函式隱式指定較高的值,則使用該容量。
static final int MAXIMUM_CAPACITY = 1 << 30;

// 建構函式沒有指定載入因子時的預設值
static final float DEFAULT_LOAD_FACTOR = 0.75
f; // 當新增節點時,節點數至少達到這個臨界值,才將連結串列轉換成樹 static final int TREEIFY_THRESHOLD = 8; /** * The bin count threshold for untreeifying a (split) bin during a * resize operation. Should be less than TREEIFY_THRESHOLD, and at * most 6 to mesh with shrinkage detection under removal. */ static final int UNTREEIFY_THRESHOLD = 6; /** * The smallest table capacity for which bins may be treeified. * (Otherwise the table is resized if too many nodes in a bin.) * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts * between resizing and treeification thresholds. */ static final int MIN_TREEIFY_CAPACITY = 64;

沒有看明白所有的引數所代表的意義,先記著,後續或許會有更多領悟(2018.6.6)。
不過可以肯定的是,如果在某個桶上面的節點個數大於了8個,會將其從連結串列結構轉換成樹結構。

transient Node<K,V>[] table;

這是HashMap中放桶的位置。也就是說,通過hash函式,計算出hash值後,將該節點放置到該陣列中的hash位置待分析),如果已經存在了,那麼就連結到連結串列上(連結到連結串列的頭部還是尾部,有待繼續分析)。

連結串列節點

先看連結串列節點的資料結構。這是一個單鏈表,其中包括了多項資訊,諸如鍵、值、hash值以及下一個節點的引用。

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;// hash值
    final K key;// 鍵
    V value;// 值
    Node<K,V> next;// 單向連結串列,指向下一個節點

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }
    // 這個hash值計算的是整個鍵值對的hash值
    public final int hashCode() {
        // key的hash與value的hash相與
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&// 鍵相同
                Objects.equals(value, e.getValue()))// 值相同
                return true;
        }
        return false;
    }
}

上述程式碼中,使用的Objects的相應方法如下:

public static int hashCode(Object o) {
    return o != null ? o.hashCode() : 0;
}
public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}

建構函式

共3個。分別用於指定相應的載入因子與起始容量。如下:

// 指定載入因子與起始容量
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;
    this.threshold = tableSizeFor(initialCapacity);
}
// 指定起始容量,載入因子預設
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 全部預設
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

在第一個建構函式中,我們仔細看看其中的三個if語句,第一個與第三個都是判斷異常情況,中間那句的意思是,如果超出了MAXIMUM_CAPACITY,那麼將起始容量置為MAXIMUM_CAPACITY。之後還會計算一個值threshold,這個值是下次resize時,需要擴充套件到的容量。其計算方式如下:

 // 對給定的容量,比它大的2^n值
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

這位運算有點抽象,弄幾個二進位制進去試試,然後對比一下就知道它大概是執行什麼樣的操作了。總結來說,就是通過無符號右移與相與,可以讓原來數從最高位開始,到最低位,全部變成1,也就是大於原數的2^n-1,後面加1,即可達到2^n,從而找到最下次resize的容量。如下:

數值 操作
1000 1000 >>> 1 = 0100
1000 | 0100 = 1100

1100 >>> 2 = 0011
1100 | 0011 = 1111

1111 >>> 4 = 0000
1111 | 0000 = 1111
0100 0100 >>> 1 = 0010
0100 | 0010 = 0110

0110 >>> 2 = 0001
0110 | 0001 = 0111

0111 >>> 4 = 0000
0111 | 0000 = 0111

試了幾組數,輸出如下:

capacity tableSizeFor()
1 1
3 4
9 16
24 32

回過頭去想想,建構函式裡面沒有對本例項中的容量做任何修改。我們可以通過反射來檢視其中的capacity值是多少,如下:

public static void main(String[] args) {
    HashMap<String, Integer> map = new HashMap<>(12, 0.74f);
    printHashMapCapacity(map);
}
public static void printHashMapCapacity(HashMap map){
 if (map == null)
     throw new IllegalArgumentException("what the fu*king arguments for : "+map);
 Class<HashMap> clz = HashMap.class;
    try {
        Method method = clz.getDeclaredMethod("capacity");
        method.setAccessible(true);
        System.out.println("capacity : "+(int)method.invoke(map)+", size : "+map.size());
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}
---
capacity : 16, size : 0

是不是有點疑問?疑問在於capacity()。我們可以看到,這個函式是在做了一系列判斷後,才給出的一個值。所以有可能是直接傳的threshold

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

增加鍵值對

先從public V put(K key, V value)開始,假設我們map.put("first", 1);。我們將計算出key的hash值,並跳轉到putVal中。

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

其中傳入了兩個boolean變數,第一個表示的意思是,如果已存在,則覆蓋;第二個表示的意識是,非構建模式(creation mode,沒太清楚是啥)。我們先看hash()的計算方式:

/**
* Computes key.hashCode() and spreads (XORs) higher bits of hash
* to lower.  Because the table uses power-of-two masking, sets of
* hashes that vary only in bits above the current mask will
* always collide. (Among known examples are sets of Float keys
* holding consecutive whole numbers in small tables.)  So we
* apply a transform that spreads the impact of higher bits
* downward. There is a tradeoff between speed, utility, and
* quality of bit-spreading. Because many common sets of hashes
* are already reasonably distributed (so don't benefit from
* spreading), and because we use trees to handle large sets of
* collisions in bins, we just XOR some shifted bits in the
* cheapest possible way to reduce systematic lossage, as well as
* to incorporate impact of the highest bits that would otherwise
* never be used in index calculations because of table bounds.
*/
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

java中int是32位的。鍵為時就為0,否則先得到鍵的hashCode(),然後其無符號右移16位後,再與原數異或。這之後才得到鍵的hash值。為什麼要這樣算。。沒太明白,所以還是把註釋貼上去吧。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 如果桶陣列為空,那麼將利用resize()初始化一個桶陣列
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 此處的位置,為什麼還需要將hash與length-1?所以位置不是hash值?
    // 好像不太對,n = 2^m,所以n-1應該是全1的某個數,如7,15。
    // 因此位置還應該是hash值。
    if ((p = tab[i = (n - 1) & hash]) == null)// 位置上不存在鍵值對
        tab[i] = newNode(hash, key, value, null);// 新建一個
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;// 若鍵值對的hash,key都相同,則將p暫存到e中
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {// 鍵值不相同,從next遍歷後續連結串列,如果存在就替換,不存在就新增到其上。
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {// 沒有後續節點
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);// 怕是要變身,後面再看
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;// 選擇跳出,說明找到相同的鍵,並將該節點暫存到e中
                p = e;// p指向e,即p.next,也就是p節點的下一個
            }
        }
        if (e != null) { // e不為空說明鍵已存在
            V oldValue = e.value;
            // 之前傳進來的值起作用了。傳進來的是false,此時會替換。
            // 如果是true的話,且舊值為空,還是會替換。
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);// 一個回撥
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();// 同樣的,要變身
    afterNodeInsertion(evict);// 還是一個回撥
    return null;
}

初始化或者將容量加倍的resize()。這個過程詐看有點懵,看了這篇部落格之後,有一種茅塞頓開的感覺,寫得非常詳細,推薦部落格。該段程式碼主要分成兩部分,前半部分是計算新的容量與臨界值,後半部分為將原桶陣列中的節點,按照相應的規律分配到新陣列中。

/**
 * Initializes or doubles table size.  If null, allocates in
 * accord with initial capacity target held in field threshold.
 * Otherwise, because we are using power-of-two expansion, the
 * elements from each bin must either stay at same index, or move
 * with a power of two offset in the new table.
 *
 * @return the table
 */
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)//該桶沒有後續的節點
                    // 新陣列上的位置可能與之前不同,可能是二倍
                    // newCap-1為0b11...11樣式的二進位制,比oldCap-1多一位1
                    // 相與的話,取決於hash值的前一位。所以可能是相同,可能是(原位置➕原容量)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    // 對擁有後續連結串列的桶,另做處理
                    // lo應該是low的縮寫,hi是high的縮寫,表示0或1
                    // 哪裡的0或1呢?這是關鍵點,繼續看
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    // 遍歷該桶的所有節點
                    do {
                        next = e.next;
                        // e.hash & oldCap這個與之前的位置有什麼差別呢?
                        // 之前算位置時,是用oldCap-1與hash相與,也就是1111...1樣式的二進位制,
                        // 現在是10000..00樣式的二進位制,所以等不等於0
                        // 取決於hash值的前1位。
                        if ((e.hash & oldCap) == 0) {
                            // 為什麼要用這個loTail?也就是尾。後續需要將尾位置上的節點的next指向e,也就是else所執行的。
                            if (loTail == null)// 說明還沒有資料
                                loHead = e;// 首
                            else
                                loTail.next = e;// 尾->next
                            loTail = e;// 尾 = e;
                        }
                        else {
                            // 邏輯同上,此時位置的二進位制的第一位是1
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    // 組成了一個新的連結串列,位置在之前的位置的位置上,因為第1位為0
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    // 組成一個新的連結串列,位置是原位置+原容量,因為第1位為1
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

為了更好地理解上述位置的變換,就將所推薦部落格中的圖片引用過來,在此對原作者表示感謝。如下:
這裡寫圖片描述
這裡寫圖片描述
總的來看,對於一個沒有後續節點的桶元素來說,那麼它在新桶陣列中的位置,可能與原桶陣列中的位置相同,也有可能是原來的兩倍;否則,將對該桶節點的所有連結串列元素都進行重新的位置規劃,要麼在原位置,要麼在(原位置➕原容量)。

查詢元素

我們先是通過常見的get(Object key)方法來獲取相應的值,該方法如下:

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

於是我們接著往下看getNode。這個方法接受兩個引數,一個是kek的hash值,一個是key,雙重保證,也在一定程度上可以加快速度吧。感覺從新增過來,看到這個獲取的過程,覺得是理所應當這樣寫。

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

刪除鍵值對

刪除鍵值對,分兩步,第一步是找到對應的鍵值對,第二步刪除該鍵值對。刪除時,可能會有兩種情況(不考慮樹結構),位於桶陣列上或者位於後續的連結串列上。對於後者,因為此連結串列是單鏈表,我們需要將該鍵值對的前一個節點記錄下來。

final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        Node<K,V> node = null, e; K k; V v;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
            if (p instanceof TreeNode)
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            else {
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        // 尋找鍵值對完畢,如果沒找到node為null
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            else if (node == p)// 在桶陣列上
                tab[index] = node.next;
            else// 在連結串列上,將前個節點的後續改成該鍵值對的後續,即實現刪除了該鍵值對
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

至此,對HashMap原始碼的分析上半部分基本上完成了,後續的中下,將對紅黑樹在其中的應用做出分析。

相關推薦

HashMap原始碼分析·

感覺HashMap才是集大成者啊 繼承關係簡要圖 HashMap類前註釋(搓翻譯) 挑重點看,挑重點翻譯~ 一種基於散列表的Map介面實現。允許null值與null鍵。HashMap與HashTable大致相同,區別在於前者是

HashMap原始碼分析

HashMap是一種散列表,通過陣列加連結串列的方式實現。在JDK1.8版本後新增加了紅黑樹結構來提高效率。 HashMap HashMap是Java的Map介面的一種基礎雜湊表實現。它提供了Map的所有操作,並且支援以null為key或value。(除了是非同步的以及支援null以外,HashMap與H

HashMap原始碼分析(史最詳細的原始碼分析

HashMap簡介 HashMap是開發中使用頻率最高的用於對映(鍵值對 key value)處理的資料結構,我們經常把hashMap資料結構叫做雜湊連結串列;  ObjectI entry<Key,Value>,entry<Key,Value>] 可以將資料通過鍵值對形

java集合之----HashMap原始碼分析(基於JDK1.7與1.8)

一、什麼是HashMap 百度百科這樣解釋: 簡而言之,HashMap儲存的是鍵值對(key和value),通過key對映到value,具有很快的訪問速度。HashMap是非執行緒安全的,也就是說在多執行緒併發環境下會出現問題(死迴圈) 二、內部實現 (1)結構 HashM

HashMap原始碼分析

目錄 目錄 1. 概述 HashMap是一種key/value形式的儲存結構. 它綜合了陣列(查詢容易, 插入和刪除困難)和連結串列(插入和刪除容易, 查詢困難)的特點. HashMap的核心點就是hash演算法和紅黑樹演算法. HashMap是無序的. 2. 儲存結構 HashMap的儲存結構為陣列 +

開發日常小結(32):HashMap 原始碼分析

2018年10月05日 目錄 1、Java資料結構圖 Java中有幾種常用的資料結構,主要分為Collection和map兩個主要介面(介面只提供方法,並不提供實現),而程式中最終使用的資料結構是繼承自這些介面的資料結構類

通俗易懂的JDK1.8中HashMap原始碼分析(歡迎探討指正)+ 典型面試題

面試題在最下面 說到HashMap之前,閱讀ArrayList與LinkedList的原始碼後做個總結 ArrayList 底層是陣列,查詢效率高,增刪效率低 LinkedList底層是雙鏈表,查詢效率低,增刪效率高   這裡只是總結看完原始碼後對hashm

HashMap原始碼分析(一)

之前有寫到ArrayList的原始碼分析,歡迎大家點開我的頭像檢視 對於HashMap這個類大家一定不陌生,想必多多少少用過或者瞭解過,今天我來和大家談談HashMap的原始碼,JDK為1.8 繼承AbstractMap類,實現map介面等等 當你不設定它的容量時預設的容量大小&n

【Java】HashMap原始碼分析——基本概念

在JDK1.8後,對HashMap原始碼進行了更改,引入了紅黑樹。 在這之前,HashMap實際上就是就是陣列+連結串列的結構,由於HashMap是一張雜湊表,其會產生雜湊衝突,為了解決雜湊衝突,HashMap採用了開鏈法,即對於用物件hashCode值計算雜湊

【Java】HashMap原始碼分析——常用方法詳解

上一篇介紹了HashMap的基本概念,這一篇著重介紹HasHMap中的一些常用方法:put()get()**resize()** 首先介紹resize()這個方法,在我看來這是HashMap中一個非常重要的方法,是用來調整HashMap中table的容量的,在很多操作中多需要重新計算容量。原始碼如下: 1

HashMap原始碼分析-jdk1.6和jdk1.8的區別

在java集合中,HashMap是用來存放一組鍵值對的數,也就是key-value形式的資料,而在jdk1.6和jdk1.8的實現有所不同。 JDK1.6的原始碼實現: 首先來看一下HashMap的類的定義: HashMap繼承了AbstractHashMap,實現

Jdk1.7 HashMap原始碼分析與思考

HashMap類圖 根據類圖可知,HashMap實現了三個介面繼承了一個抽象類。 實現的介面概覽 介面如下: java.util.Map 其中Map介面中定義了Map基本操作方法,詳細介面描述請參考java.util.Map介面描述. Map介面中採

JDK1.8 HashMap原始碼分析 ----轉載別人的,以後好複習。

本人看不懂原始碼,邏輯思維差,又懶。連看文件都喜歡跳字閱讀。所以只能去看別人寫的原始碼分析。也不知道能不能轉載。。所以直接貼個地址。 這是幾天下來,翻了好多篇部落格,發現寫的非常詳細,而且步驟和註釋寫的非常清晰的一篇了。。 大神好厲害。拜讀兩遍,以表敬意。 讀技

HashMap原始碼分析(JDK1.8)

一、HashMap簡介  基於雜湊表的 Map 介面的實現。此實現提供所有可選的對映操作,並允許key和value為null(但是隻能有一個key為null,且key不能重複,value可以重複)。(除了非同步和允許使用 null 之外,HashMap 類與 Hashtab

HashMap原始碼分析(JDK1.8)- 你該知道的都在這裡了

       HashMap是Java和Android程式設計師的基本功, JDK1.8對HashMap進行了優化, 你真正理解它了嗎? 考慮如下問題:  1、雜湊基本原理?(答:散列表、hash碰撞、連結串列、紅黑樹)2、hashmap查詢的時間複雜度, 影響因素和原理?

Java BAT大型公司面試必考技能視訊教程之HashMap原始碼分析與實現

視訊通過以下四個方面介紹了HASHMAP的內容 一、 什麼是HashMap Hash雜湊將一個任意的長度通過某種演算法(Hash函式演算法)轉換成一個固定的值。 MAP:地圖 x,y 儲存 總結:通過HASH出來的值,然後通過值定位到這個MAP,然後value儲存到這個M

HashMap原始碼分析(四)put-jdk8-紅黑樹的引入

HashMap jdk8以後他的邏輯結構發生了一點變化: 大概就是這個意思: 當某一個點上的元素數量打到一定的閾值的時候,連結串列會變成一顆樹,這樣在極端情況下(所有的元素都在一個點上,整個就以連結串列),一些操作的時間複雜度有O(n)變成了O(logn)。 分析原始碼

jdk1.8 HashMap原始碼分析(resize函式)

final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.len

java集合(4):HashMap原始碼分析(jdk1.8)

前言 Map介面雖然也是集合體系中的重要一個分支,但是Map介面並不繼承自Collection,而是自成一派。 public interface Map<K,V> Map集合儲存鍵對映到值的物件。一個集合中不能包含重複的鍵,每個鍵最多

Java HashMap原始碼分析

put/get方法 package java.util; import java.io.*; public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Se