HashMap原始碼分析·上
感覺HashMap才是集大成者啊
繼承關係簡要圖
HashMap類前註釋(搓翻譯)
挑重點看,挑重點翻譯~
一種基於散列表的Map
介面實現。允許null
值與null
鍵。HashMap
與HashTable
大致相同,區別在於前者是非同步且允許null
。不保證順序,且順序可能會變。
如果hash函式足夠好,這種實現中的基礎操作(如get
、put
)只需常量時間即可。選擇初始容量與載入因子非常重要,如果你非常在意Iterator
的表現。
一個HashMap
例項擁有兩個影響它的效能的因素:初始容量和載入因子。初始容量就是在hash表建立時桶的個數;載入因子是一種衡量雜湊表所允許的最大容量的引數,也就是capacity * 載入因子
通常來說,預設的載入因子0.75可以在時間消耗和空間消耗之間取得一個較好的平衡。過高,會減少空間消耗但會增加檢視消耗(表現在HashMap
中的大部分操作,包括get
和put
)。當設定它的初始容量時,為了減少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