Java HashMap源碼分析與手寫

1. HashMap是怎樣一種數據結構

數組+ 鏈表 + 紅黑樹(jdk1.8引入,後面也是按照jdk1.8的來分析)

 

2. HashMap爲什麼取值快?
 

    HashMap在存儲的時候,根據key的hash值(實際上還右移16再^hash值)&上數組的長度-1得到了在數組上的散列位置index,同樣的方式,在取值的時候,和存儲方式一致取得key值在數組上的散列位置index, 假如不存在散列碰撞的情況下,時間複雜度爲O(1),效率很高
假如散列衝突比較多,將形成鏈表n個數據,則需要遍歷鏈表,時間複雜度增大,查找的時間複雜度爲O(n) (jdk1.8引入紅黑樹減小了因衝突查找效率低的問題,時間複雜度爲log(n))。

   key的hash值處理代碼:  

      static final int hash(Object key) {
         int h;
          return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
      }

   爲什麼不直接用hash值去使用?

   因爲hash值比較大,比較耗內存。>>>即無符號右移,將得到高位的小的數字,^爲異或,^的時候爲右移的hash值高位都爲0, ^的結果高位都會被處理掉。

 爲什麼用&來取代%來得到在數組的散列位置?

  因爲&比%解釋更快,hashmap中很多用到運算符代替數學運算符,如<<1 來代替乘以2,因爲速度更快。

 

3. 爲什麼有些時候在知道要存儲數量的時候,初始化HashMap的時候建議給容量大小初始化大小?

   這就涉及到HashMap的擴容了。在沒有給定HashMap,當數組大小達到閥值threthreshold = 容量大小 * 加載因子(默認0.75)的時候,就需要進行擴容了,原來的數組長度乘以2擴容出新的數組,而擴容之後做的事情也比較耗費時間,也就是需要重新再做hash值散列到新的數組時間複雜度爲O(n),假如只要放100個數據,不初始化容量的話,將擴容很多次,效率來說是很低的。其實我們可以在new Hash(100),的時候就傳如初始化值,將減少擴容。當然也不能初始化容量太大,也比較浪費內存。傳入100,那他的容量就初始化爲100了?請看初始化容量大小代碼。

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;
}

 

假如傳如cap 那得到的值將是大於cap而接近2的n次方的,假如傳入3,將得到4(2的2次方),傳入10,的到16(2的4次方)

HashMap的大小爲什麼是2的n次方   

   爲了散列key在數組上面,採用處理過的hash&length-1(length是數組的長度),得到在數組的散列位置。
   在&運算的時候,使得length-1 的二進制表示每一位都等於1,使得hash能夠均勻的散列在數組上,這是一個有效減少散列碰撞的方法



4. jdk1.8之後,HashMap爲爲何引入紅黑樹? 

    在jdk1.7之前,key散列到數組上的發生衝突碰撞的解決方式就是使用鏈表來解決,用鏈表來解決會存在一些問題

    1. 鏈表的長度越長,查找的時間複雜度就越大,假如長度n,時間複雜度爲O(n)。
    2. 多線程的情況下出現鏈表閉環(當然在多線程的情況下我們不建議用HashMap,可以使用ConcurrentHashMap)

    在鏈表長度大於8的時候,鏈表會被轉換爲紅黑樹,紅黑樹是一個帶顏色的自平衡二叉搜索樹,時間複雜度O(log(n)),長度小於6的時候轉化爲鏈表


5. 手寫HashMap

/**
 * 文件描述:手寫實現hashmap,不考慮邊界情況,只用鏈表去解決衝突的實現
 * 作者:chenjingkun708
 * 創建時間:2020/3/20
 * 更改時間:2020/3/20
 */
public class MyHashMap<K,V>{

    private int threshold;//閥值
    private float loadFactor = 0.75f;//加載因子
    private Node<K,V>[] array;
    private String TAG = "android_test";
    private int size;
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默認大小16
    public MyHashMap(){

    }

    public V put(K key,V value){
        //實際上可存在一個key爲null
         if (key==null){
             return null;
         }
         //1.得到hash值的處理
         int h = hash(key);
//         Log.i(TAG,"處理過的hashcode="+h);
         //2. 得到數組長度-1
         Node<K,V>[] tab;
        Node<K,V> newNode;

         if ((tab=array)==null){
             tab = resize();
         }
        int index = h & tab.length-1;
         //考慮一,散列不衝突的情況
        Node<K,V> first = tab[index];
        if (first==null){
             newNode = new Node<>(key,value,h,null);
            tab[index] = newNode;
        }else {
            Node<K,V> cur = first;
            //查找鏈表有沒有存在key相同的
            while (cur!=null){
                if (cur.hash==h&&(cur.key==key || cur.key.equals(key))){
                    //查找到key相同的,覆蓋
                    cur.value = value;
                    return value;
                }
                if (cur.next==null){
                    break;
                }
                cur = cur.next;
            }
            //到了這裏就是沒有查找到了
            newNode = new Node<>(key,value,h,null);
            cur.next = newNode;
        }
        if (++size>threshold){
            resize();
        }
        return value;
    }

    //擴容
    private Node<K,V>[] resize() {
        int oldCap,newCap = 0;
        Node[] oldArray;
        oldCap = array==null?0:array.length;
        if (oldCap>0){
           newCap = array.length << 1;
        }else {
            newCap = DEFAULT_INITIAL_CAPACITY;
        }
        threshold = (int) (newCap*loadFactor);
        oldArray = array;
        array = new Node[newCap];
        //要散列老的到新的上面
        if (oldArray!=null&&oldArray.length>0){
            for (Node node:oldArray){
                if (node!=null){
                    while (node!=null){
                        int index = node.hash & newCap-1;
                        Node next = node.next;
                        node.next = null;
                        if (array[index]==null){
                            array[index] = node;
                        }else {
                            Node first = array[index];
                            while (first!=null){
                                if (first.next==null){
                                    first.next = node;
                                    break;
                                }
                                first = first.next;
                            }
                        }
                        node = next;
                    }

                }
            }

        }
        return array;
    }

    //處理hashcode
    private int hash(K key) {
        int h;
//        Log.i(TAG,"hashcode="+key.hashCode());
        return key==null?0:((h = key.hashCode())^ (h >>>16));
    }

    public int size(){
        return size;
    }

    public V get(K key){
        //實際上可以存在一個key爲null的
        if (key==null||array==null){
            return null;
        }
        int h = hash(key);
        int index = h & array.length-1;
        Node<K,V> node = array[index];
        while (node!=null){
            if (node.hash==h&&(key==node.key||node.key.equals(key))){
                return node.value;
            }
            node = node.next;
        }
        return null;
    }

    //打印所有值
    public void printAll(){
        if (array!=null){
            for (Node<K,V> node : array){
                while (node!=null){
                    Log.i(TAG,"key="+node.key+",value="+node.value);
                    node = node.next;
                }
            }
        }else {
            Log.i(TAG,"數組爲空");
        }
    }

    //數據節點
    static class Node<K,V>{
        private K key;
        private V value;
        private int hash;
        private Node<K,V> next;

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

 

測試:
 

MyHashMap myHashMap = new MyHashMap<>();
myHashMap.put('a',1);
myHashMap.put('b',2);
myHashMap.put("ab",3);
myHashMap.put("abcd",4);
myHashMap.put("adfg",5);
myHashMap.put("a1","a1");
myHashMap.put("a1","aa");
myHashMap.put("a2","a2");
myHashMap.put("a3","a3");
myHashMap.put("a4","a4");
myHashMap.put("a5","a5");
myHashMap.put("a6","a6");
myHashMap.put("a7","a7");
myHashMap.put("a8","a8");
myHashMap.put('a','a');

myHashMap.printAll();
int num = (int) myHashMap.get("adfg");
Log.i("android_test","取得數字:"+num);
Log.i("android_test","打印大小:"+myHashMap.size());

 

查看打印:

2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a,value=a
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=ab,value=3
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=b,value=2
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=adfg,value=5
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=abcd,value=4
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a1,value=aa
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a2,value=a2
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a3,value=a3
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a4,value=a4
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a5,value=a5
2020-03-20 18:10:38.398 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a6,value=a6
2020-03-20 18:10:38.398 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a7,value=a7
2020-03-20 18:10:38.398 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a8,value=a8
2020-03-20 18:10:38.398 14791-14791/example.ndk.cjk.datastructdemo I/android_test: 取得數字:5
2020-03-20 18:10:38.398 14791-14791/example.ndk.cjk.datastructdemo I/android_test: 打印大小:13

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章