日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

HashMap方法源码

發(fā)布時間:2024/2/28 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 HashMap方法源码 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

三種構造函數

public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR); }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); }static final int tableSizeFor(int cap) {// 擴容門檻為傳入的初始容量往上取最近的2的n次方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; }

?put(K key, V value)方法

public V put(K key, V value) {// 調用hash(key)計算出key的hash值return putVal(hash(key), key, value, false, true); }static final int hash(Object key) {int h;// 如果key為null,則hash值為0,否則調用key的hashCode()方法// 并讓高16位與整個hash異或,這樣做是為了使計算出的hash更分散return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K, V>[] tab;Node<K, V> p;int n, i;// 如果桶的數量為0,則初始化if ((tab = table) == null || (n = tab.length) == 0)// 調用resize()初始化n = (tab = resize()).length;// (n - 1) & hash 計算元素在哪個桶中// 如果這個桶中還沒有元素,則把這個元素放在桶中的第一個位置if ((p = tab[i = (n - 1) & hash]) == null)// 新建一個節(jié)點放在桶中tab[i] = newNode(hash, key, value, null);else {// 如果桶中已經有元素存在了Node<K, V> e;K k;// 如果桶中第一個元素的key與待插入元素的key相同,保存到e中用于后續(xù)修改value值if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)// 如果第一個元素是樹節(jié)點,則調用樹節(jié)點的putTreeVal插入元素e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);else {// 遍歷這個桶對應的鏈表,binCount用于存儲鏈表中元素的個數for (int binCount = 0; ; ++binCount) {// 如果鏈表遍歷完了都沒有找到相同key的元素,說明該key對應的元素不存在,則在鏈表最后插入一個新節(jié)點if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);// 如果插入新節(jié)點后鏈表長度大于8,則判斷是否需要樹化,因為第一個元素沒有加到binCount中,所以這里-1if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}// 如果待插入的key在鏈表中找到了,則退出循環(huán)if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}// 如果找到了對應key的元素if (e != null) { // existing mapping for key// 記錄下舊值V oldValue = e.value;// 判斷是否需要替換舊值if (!onlyIfAbsent || oldValue == null)// 替換舊值為新值e.value = value;// 在節(jié)點被訪問后做點什么事,在LinkedHashMap中用到afterNodeAccess(e);// 返回舊值return oldValue;}}// 到這里了說明沒有找到元素// 修改次數加1++modCount;// 元素數量加1,判斷是否需要擴容if (++size > threshold)// 擴容resize();// 在節(jié)點插入后做點什么事,在LinkedHashMap中用到afterNodeInsertion(evict);// 沒找到元素返回nullreturn null; }

首先說下擾動函數hash()函數,如果key為null,那么直接返回0,不為null的話,利用哈希值的高16位與低16位進行異或。

關鍵還是得看putVal()方法,我在這總結一下:

(1)計算key的hash值;

(2)如果桶(數組)數量為0,則初始化桶;

(3)如果key所在的桶沒有元素,則直接插入;

(4)如果key所在的桶中的第一個元素的key與待插入的key相同,說明找到了元素,轉后續(xù)流程(9)處理;

(5)如果第一個元素是樹節(jié)點,則調用樹節(jié)點的putTreeVal()尋找元素或插入樹節(jié)點;

(6)如果不是以上三種情況,則遍歷桶對應的鏈表查找key是否存在于鏈表中;

(7)如果找到了對應key的元素,則轉后續(xù)流程(9)處理;

(8)如果沒找到對應key的元素,則在鏈表最后插入一個新節(jié)點并判斷是否需要樹化;

(9)如果找到了對應key的元素,則判斷是否需要替換舊值,并直接返回舊值;

(10)如果插入了元素,則數量加1并判斷是否需要擴容;

?resize()方法

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)// 如果舊容量的兩倍小于最大容量并且舊容量大于默認初始容量(16),則容量擴大為兩部,擴容門檻也擴大為兩倍newThr = oldThr << 1; // double threshold} else if (oldThr > 0) // initial capacity was placed in threshold// 使用非默認構造方法創(chuàng)建的map,第一次插入元素會走到這里// 如果舊容量為0且舊擴容門檻大于0,則把新容量賦值為舊門檻newCap = oldThr;else { // zero initial threshold signifies using defaults// 調用默認構造方法創(chuàng)建的map,第一次插入元素會走到這里// 如果舊容量舊擴容門檻都是0,說明還未初始化過,則初始化容量為默認容量,擴容門檻為默認容量*默認裝載因子newCap = DEFAULT_INITIAL_CAPACITY;newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr == 0) {// 如果新擴容門檻為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;// 如果桶中第一個元素不為空,賦值給eif ((e = oldTab[j]) != null) {// 清空舊桶,便于GC回收 oldTab[j] = null;// 如果這個桶中只有一個元素,則計算它在新桶中的位置并把它搬移到新桶中// 因為每次都擴容兩倍,所以這里的第一個元素搬移到新桶的時候新桶肯定還沒有元素if (e.next == null)newTab[e.hash & (newCap - 1)] = e;else if (e instanceof TreeNode)// 如果第一個元素是樹節(jié)點,則把這顆樹打散成兩顆樹插入到新桶中去((TreeNode<K, V>) e).split(this, newTab, j, oldCap);else { // preserve order// 如果這個鏈表不止一個元素且不是一顆樹// 則分化成兩個鏈表插入到新的桶中去// 比如,假如原來容量為4,3、7、11、15這四個元素都在三號桶中// 現在擴容到8,則3和11還是在三號桶,7和15要搬移到七號桶中去// 也就是分化成了兩個鏈表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) == 0的元素放在低位鏈表中// 比如,3 & 4 == 0if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;} else {// (e.hash & oldCap) != 0的元素放在高位鏈表中// 比如,7 & 4 != 0if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);// 遍歷完成分化成兩個鏈表了// 低位鏈表在新桶中的位置與舊桶一樣(即3和11還在三號桶中)if (loTail != null) {loTail.next = null;newTab[j] = loHead;}// 高位鏈表在新桶中的位置正好是原來的位置加上舊容量(即7和15搬移到七號桶了)if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab; }

?

(1)如果使用是默認構造方法,則第一次插入元素時初始化為默認值,容量為16,擴容門檻為12;

(2)如果使用的是非默認構造方法,則第一次插入元素時初始化容量等于擴容門檻,擴容門檻在構造方法里等于傳入容量向上最近的2的n次方;

(3)如果舊容量大于0,則新容量等于舊容量的2倍,但不超過最大容量2的30次方,新擴容門檻為舊擴容門檻的2倍;

(4)創(chuàng)建一個新容量的桶;

(5)搬移元素,原鏈表分化成兩個鏈表,低位鏈表存儲在原來桶的位置,高位鏈表搬移到原來桶的位置加舊容量的位置;

TreeNode.putTreeVal(…)方法

final TreeNode<K, V> putTreeVal(HashMap<K, V> map, Node<K, V>[] tab,int h, K k, V v) {Class<?> kc = null;// 標記是否找到這個key的節(jié)點boolean searched = false;// 找到樹的根節(jié)點TreeNode<K, V> root = (parent != null) ? root() : this;// 從樹的根節(jié)點開始遍歷for (TreeNode<K, V> p = root; ; ) {// dir=direction,標記是在左邊還是右邊// ph=p.hash,當前節(jié)點的hash值int dir, ph;// pk=p.key,當前節(jié)點的key值K pk;if ((ph = p.hash) > h) {// 當前hash比目標hash大,說明在左邊dir = -1;}else if (ph < h)// 當前hash比目標hash小,說明在右邊dir = 1;else if ((pk = p.key) == k || (k != null && k.equals(pk)))// 兩者hash相同且key相等,說明找到了節(jié)點,直接返回該節(jié)點// 回到putVal()中判斷是否需要修改其value值return p;else if ((kc == null &&// 如果k是Comparable的子類則返回其真實的類,否則返回null(kc = comparableClassFor(k)) == null) ||// 如果k和pk不是同樣的類型則返回0,否則返回兩者比較的結果(dir = compareComparables(kc, k, pk)) == 0) {// 這個條件表示兩者hash相同但是其中一個不是Comparable類型或者兩者類型不同// 比如key是Object類型,這時可以傳String也可以傳Integer,兩者hash值可能相同// 在紅黑樹中把同樣hash值的元素存儲在同一顆子樹,這里相當于找到了這顆子樹的頂點// 從這個頂點分別遍歷其左右子樹去尋找有沒有跟待插入的key相同的元素if (!searched) {TreeNode<K, V> q, ch;searched = true;// 遍歷左右子樹找到了直接返回if (((ch = p.left) != null &&(q = ch.find(h, k, kc)) != null) ||((ch = p.right) != null &&(q = ch.find(h, k, kc)) != null))return q;}// 如果兩者類型相同,再根據它們的內存地址計算hash值進行比較dir = tieBreakOrder(k, pk);}TreeNode<K, V> xp = p;if ((p = (dir <= 0) ? p.left : p.right) == null) {// 如果最后確實沒找到對應key的元素,則新建一個節(jié)點Node<K, V> xpn = xp.next;TreeNode<K, V> x = map.newTreeNode(h, k, v, xpn);if (dir <= 0)xp.left = x;elsexp.right = x;xp.next = x;x.parent = x.prev = xp;if (xpn != null)((TreeNode<K, V>) xpn).prev = x;// 插入樹節(jié)點后平衡// 把root節(jié)點移動到鏈表的第一個節(jié)點moveRootToFront(tab, balanceInsertion(root, x));return null;}} }

(1)尋找根節(jié)點;

(2)從根節(jié)點開始查找;

(3)比較hash值及key值,如果都相同,直接返回,在putVal()方法中決定是否要替換value值;

(4)根據hash值及key值確定在樹的左子樹還是右子樹查找,找到了直接返回;

(5)如果最后沒有找到則在樹的相應位置插入元素,并做平衡;

treeifyBin()方法

final void treeifyBin(Node<K, V>[] tab, int hash) {int n, index;Node<K, V> e;if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)// 如果桶數量小于64,直接擴容而不用樹化// 因為擴容之后,鏈表會分化成兩個鏈表,達到減少元素的作用// 當然也不一定,比如容量為4,里面存的全是除以4余數等于3的元素// 這樣即使擴容也無法減少鏈表的長度resize();else if ((e = tab[index = (n - 1) & hash]) != null) {TreeNode<K, V> hd = null, tl = null;// 把所有節(jié)點換成樹節(jié)點do {TreeNode<K, V> p = replacementTreeNode(e, null);if (tl == null)hd = p;else {p.prev = tl;tl.next = p;}tl = p;} while ((e = e.next) != null);// 如果進入過上面的循環(huán),則從頭節(jié)點開始樹化if ((tab[index] = hd) != null)hd.treeify(tab);} } final void treeify(Node<K, V>[] tab) {TreeNode<K, V> root = null;for (TreeNode<K, V> x = this, next; x != null; x = next) {next = (TreeNode<K, V>) x.next;x.left = x.right = null;// 第一個元素作為根節(jié)點且為黑節(jié)點,其它元素依次插入到樹中再做平衡if (root == null) {x.parent = null;x.red = false;root = x;} else {K k = x.key;int h = x.hash;Class<?> kc = null;// 從根節(jié)點查找元素插入的位置for (TreeNode<K, V> p = root; ; ) {int dir, ph;K pk = p.key;if ((ph = p.hash) > h)dir = -1;else if (ph < h)dir = 1;else if ((kc == null &&(kc = comparableClassFor(k)) == null) ||(dir = compareComparables(kc, k, pk)) == 0)dir = tieBreakOrder(k, pk);// 如果最后沒找到元素,則插入TreeNode<K, V> xp = p;if ((p = (dir <= 0) ? p.left : p.right) == null) {x.parent = xp;if (dir <= 0)xp.left = x;elsexp.right = x;// 插入后平衡,默認插入的是紅節(jié)點,在balanceInsertion()方法里root = balanceInsertion(root, x);break;}}}}// 把根節(jié)點移動到鏈表的頭節(jié)點,因為經過平衡之后原來的第一個元素不一定是根節(jié)點了moveRootToFront(tab, root); }

?

(1)從鏈表的第一個元素開始遍歷;

(2)將第一個元素作為根節(jié)點;

(3)其它元素依次插入到紅黑樹中,再做平衡;

(4)將根節(jié)點移到鏈表第一元素的位置(因為平衡的時候根節(jié)點會改變);

get(Object key)方法

public V get(Object key) {Node<K, V> e;return (e = getNode(hash(key), key)) == null ? null : e.value; }final Node<K, V> getNode(int hash, Object key) {Node<K, V>[] tab;Node<K, V> first, e;int n;K k;// 如果桶的數量大于0并且待查找的key所在的桶的第一個元素不為空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) {// 如果第一個元素是樹節(jié)點,則按樹的方式查找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; }

(1)計算key的hash值;

(2)找到key所在的桶及其第一個元素;

(3)如果第一個元素的key等于待查找的key,直接返回;

(4)如果第一個元素是樹節(jié)點就按樹的方式來查找,否則按鏈表方式查找;

?

?

總結

以上是生活随笔為你收集整理的HashMap方法源码的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。