HashMap是Java語言中最常用的的數據結構之一,它的高效性和易用性使它成為程序員們必備的工具之一。本文將從HashMap的原理入手,詳細闡述它的實現細節和注意事項,幫助讀者掌握HashMap的使用方法和技巧。
一、什麼是HashMap
HashMap是Java語言中的一種散列表,它可以存儲鍵值對的映射關係。HashMap可以快速地進行插入、查找和刪除操作,時間複雜度均為O(1)。HashMap底層實現是通過數組和鏈表相結合的方式來實現散列表。當鏈表長度大於閾值(默認為8)時,鏈表會轉化成紅黑樹,以保證插入、查找和刪除操作的平均時間複雜度為O(1)。
二、HashMap的構造方法
以下是HashMap的幾種構造方法:
1. HashMap() : 創建一個空的HashMap 2. HashMap(int initialCapacity) : 創建指定大小的HashMap 3. HashMap(int initialCapacity, float loadFactor) : 創建指定大小和載入因子的HashMap 4. HashMap(Map m) : 創建指定Map的HashMap
其中,initialCapacity是HashMap的初始容量,loadFactor是負載因子,如果負載因子為0.75,則當HashMap的大小達到容量的75%時,會自動增加容量。如果初始容量和負載因子沒有給出,則默認初始容量為16,負載因子為0.75。
三、HashMap的put()方法
以下是HashMap的put()方法:
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node[] tab; Node p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
put()方法是非常重要的方法,它將鍵值對插入到HashMap中。它的實現細節如下:
1. 首先,通過hash()方法計算鍵的哈希值,判斷table是否為空或長度為0。如果是,通過resize()方法進行初始化,否則直接獲取table的長度n。
2. 然後,通過(n – 1) & hash計算出要插入的位置,p表示該位置的第一個節點。如果該位置為空,則直接插入鍵值對,否則執行第3步。
3. 判斷該位置上的第一個節點的key是否和要插入的key相等,如果相等則執行覆蓋操作。如果不相等,判斷該位置上的第一個節點是否為TreeNode類型。如果是,則執行紅黑樹的put操作,否則遍歷該鏈表,找到最後一個節點,插入新的節點後,如果該鏈表的長度大於等於8,則通過treeifyBin方法將該鏈錶轉化成紅黑樹。
4. 插入完成以後,判斷size是否大於threshold,如果是,則通過resize()方法進行擴容。
四、HashMap的get()方法
以下是HashMap的get()方法:
public V get(Object key) { Node e; // 通過hash方法計算hash值,然後在table數組中尋找key所在的節點 return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node getNode(int hash, Object key) { Node[] tab; Node first, e; int n; K k; // table不為空,取得table數組的長度n,n>0 if ((tab = table) != null && (n = tab.length) > 0 && // 通過(n - 1) & hash獲取key在table數組中的位置first (first = tab[(n - 1) & hash]) != null) { // 如果在table的first位置上就找到相同的key,則返回這個節點first if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; // 如果first位置沒有找到相同的key,則處理next鏈表 if ((e = first.next) != null) { // 如果first位置是TreeNode節點,則使用TreeNode的方式獲取相同的key if (first instanceof TreeNode) return ((TreeNode)first).getTreeNode(hash, key); // 否則,遍歷next鏈表查找相同的key do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; // 如果table為空或key沒有匹配到返回null }
get()方法是HashMap中的另一個重要方法,它以鍵為參數,返回與之關聯的值。get()方法的實現細節如下:
1. 首先通過getNode()方法獲取到key所在的節點。getNode()方法也是一個非常重要的方法,它遍歷了HashMap中的所有元素,尋找與key相同的元素,如果找到,則返回與之對應的節點,否則返回null。
2. 如果key所在的節點不為空,則返回該節點對應的值,否則返回null。
五、HashMap的remove()方法
以下是HashMap的remove()方法:
public V remove(Object key) { Node e; // 通過hash方法計算hash值,然後在table數組中尋找key所在的節點 return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; } final Node removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node[] tab; Node p; int n, index; // 如果table不為空,且key在table中存在,則執行刪除操作 if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node node = null, e; K k; V v; // 判斷p節點是不是要被刪除的節點,並找到p鏈表上的最後一個節點node 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)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); } } // 如果找到要被刪除的節點,則執行刪除操作 if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode)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; }
remove()方法是HashMap刪除元素的方法,它需要傳入一個key作為參數,並返回被刪除的元素的value值。remove()方法的實現細節如下:
1. 通過hash()方法計算鍵的哈希值,然後獲取該鍵所在的節點p,並獲取在該節點上的最後一個節點node。如果從p節點開始遍歷整個鏈表都沒有找到與key相同的節點,則返回null。
2. 如果找到了與key相同的節點,首先判斷該節點是否是TreeNode類型。如果是,則使用TreeNode的方式刪除節點。否則,判斷要被刪除的節點是否是p節點。如果是,則直接將p節點的下一個節點賦給tab[index]。否則,通過遍歷鏈表,將要被刪除的節點刪除掉。
3. 執行完刪除操作以後,判斷size是否小於threshold的0.25,如果是,則通過resize()方法進行縮容。
六、HashMap的注意事項
1. HashMap的線程安全性問題
HashMap是非線程安全的,如果多個線程同時對一個HashMap進行讀寫操作,可能會出現數據不一致的情況。為了解決這個問題,可以使用線程安全的ConcurrentHashMap。
2. HashMap中鍵對象的注意事項
為了保證HashMap數據的一致性,鍵對象必須實現equals()方法和hashCode()方法,這兩個方法的實現決定了HashMap的查找、插入和刪除操作的正確性和效率。在使用HashMap時,需要特別注意鍵對象的實現細節,盡量避免相同的鍵對象產生哈希碰撞的情況,從而提高HashMap的效率。
3. HashMap的默認容量和負載因子
HashMap的默認容量為16,負載因子為0.75。如果不特別指定初始容量和負載因子,則採用默認值。在實際應用中,建議根據數據量大小和訪問頻率來選擇合適的初始容量和負載因子,以提高HashMap的效率和性能。
七、總結
本文對HashMap的實現原理進行了詳細的闡述,包括構造方法、put()方法、get()方法、remove()方法等。同時,我們也介紹了HashMap的注意事項,如線程安全性問題、鍵對象的實現細節、默認容量和負載因子等。希望通過本文的學習,能夠幫助讀者更好地掌握HashMap的使用方法和技巧。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/233965.html