入坑java很多年了,現在總結一下自己學到的東西。
1、首先我們先來聊聊什么是HashMap?
什么是hash?hash用中文的說法就叫做“散列”,通俗的講就是把任意長度的字符串輸入,經過hash計算出固定長度的字符串。而這個字符串就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,而不可能從散列值來唯一的確定輸入值。
2、hash碰撞?
上面已經說到了,不同的輸入值可能會計算出相同的輸出,這就是碰撞。
3、java中的hashMap是怎么實現的?
java的hashMap底層是用數組加鏈表實現的。存儲對象的時候,我們是將K/V傳給put方法,然后這個put方法會調用hashCode方法計算出hash值(也就是數組的下標)從而得到存儲的位置,進一步存儲。那么問題來了,我們都知道,創建數組的時候是要指定數組的長度的,萬一它的初始化的數組不夠用怎么辦?其實hashMap早都給你想好了,hashMap會根據當前你存儲的數據的占用情況自動調整數組的長度。獲取對象的時候,我們是把K傳給get()方法,它就會調用hashCode計算hash的值從而得到存儲位置,並且進一步調用equals方法確定鍵值對。發生碰撞的時候:HashMap通過鏈表將產生碰撞沖突的元素組織起來,在Java8中,如果一個bucket中碰撞沖突的元素超過某個限制(默認是8),則使用紅黑樹來替換鏈表,從而提高速度。關於解決hash沖突的方法在這里暫且不說,我直接上代碼了。
4、代碼
首先我們先定義一個Map接口:
package com.jian.utils; /** * Map接口 * @author weijianyi * * @param <K> * @param <V> */ public interface Map<K,V> { //向Map中插入值 public V put(K k,V v); //根據key獲取hashMap中的值 public V get(K k); //獲得Map中元素的個數 public int size(); //獲取Map中,鍵值對的對象 interface Entry<K,V>{ K getKey(); V getValue(); V setValue(V v); } }
第二步定義我們的MyhashMap類:
package com.jian.utils; /** * hashMap的實現 數組+鏈表 * * @author weijianyi * * @param <K> * @param <V> */ public class HashMap<K, V> implements Map<K, V> { // 數據存儲的結構==>數組+鏈表 Node<K, V>[] array = null; // 數組/哈希桶的長度,就是數組初始化的長度 private static int defaultLength = 16; /**加載因子/擴容因子,就是給數組界定一個存儲閾值, 當數組沾滿整個數組的75%的時候,觸發擴容操作)**/ private static double factor = 0.75D; // hashMap中的元素個數 private int size; // put元素方法 @SuppressWarnings("unchecked") @Override public V put(K k, V v) { // 1.載機制,使用的時候進行分配 if (array == null) { //初始化一個數組,給的長度是defaultLength array = new Node[defaultLength]; } // 2.通過hash算法,計算出具體插入的位置,也就是數組的下標 int index = position(k, defaultLength); // 擴容,判斷是否需要擴容 // 擴容規則,元素的個數size 大於 桶的尺寸*加載因子 if (size > defaultLength * factor) { resize(); } // 3.放入要插入的元素(添加到鏈表) Node<K, V> node = array[index]; //先判斷一下這個鏈表的index位置是否為空 if (node == null) { //鏈表的index這個位置是空的,直接新建該鏈表並將值放入該位置 array[index] = new Node<K, V>(k, v, null); //元素的個數加自增1 size++; } else { if (k.equals(node.getKey()) || k == node.getKey()) { return node.setValue(v); } else { array[index] = new Node<K, V>(k, v, node); size++; } } return null; } // 擴容,並且重新排列元素 private void resize() { // 翻倍擴容 // 1.創建新的array臨時變量,相當於defaultlength*2 @SuppressWarnings("unchecked") Node<K, V>[] temp = new Node[defaultLength << 1]; // 2.重新計算散列值,插入到新的array中去。 code=key % defaultLength ==> code=key % // defaultLength*2 Node<K, V> node = null; for (int i = 0; i < array.length; i++) { node = array[i]; while (node != null) { // 重新散列 int index = position(node.getKey(), temp.length); // 插入鏈表的頭部 Node<K, V> next = node.next; // 3 node.next = temp[index]; // 1 temp[index] = node; // 2 node = next; } } // 3.替換掉舊的array array = temp; //更新默認的擴容因子的值 defaultLength = temp.length; temp = null; } // 計算位置 private int position(K k, int length) { int code = k.hashCode(); // 取模算法 return code % (length - 1); // 求與算法 // return code & (defaultLength-1); } /** * 用K獲取hashMap 的K對應的值 */ @Override public V get(K k) { if (array != null) { int index = position(k, defaultLength); Node<K, V> node = array[index]; // 遍歷鏈表 while (node != null) { // 如果key值相同返回value if (node.getKey() == k) { return node.getValue(); } else { // 如果key值不同則調到下一個元素 node = node.next; } } } return null; } /** * 獲取hashMap元素個數 */ @Override public int size() { return size; } // 鏈表節點(鏈表類) static class Node<K, V> implements Entry<K, V> { K key; V value; //表示下一個節點 Node<K, V> next; // 構造一個包含當前節點和下一個節點的鏈表 public Node(K key, V value, Node<K, V> next) { super(); this.key = key; this.value = value; this.next = next; } /** * 鏈表的get和set方法 */ @Override public K getKey() { return this.key; } @Override public V getValue() { return this.value; } @Override public V setValue(V v) { V oldValue = this.value; this.value = v; return oldValue; } } // 測試方法 public void print() { System.out.println("==============================="); if (array != null) { Node<K, V> node = null; for (int i = 0; i < array.length; i++) { node = array[i]; System.out.print("下標[" + i + "]"); // 遍歷鏈表 while (node != null) { System.out.print("[" + node.getKey() + ":" + node.getValue() + "]"); if (node.next != null) { node = node.next; } else { // 到尾部元素 node = null; } } System.out.println(); } } } }
到此,自己動手寫一個hashMap結束,其實並不難,只要大家認真的去看他每一個步驟和方法流程,我相信大家都能自己寫出一個。我這里只是寫了一個簡易的,如果有什么地方不對或者有什么修改意見,歡迎大家評論探討,我們一起進步。