JDK源碼閱讀系列---HashMap

JDK8的HashMap的bucket內部引入了treeify,鏈表過長(>8),影響查詢,JDK將把bucket內部元素從Node變爲TreeNode(實現紅黑樹))

HashMap做爲一個常常用到的類,先今後類開始閱讀。java

####存儲原理圖 輸入圖片說明node

####1、 成員變量數組

  1. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
    默認的桶數量app

  2. static final int MAXIMUM_CAPACITY = 1 << 30;
    桶的最大數量ui

  3. static final float DEFAULT_LOAD_FACTOR = 0.75f;
    默認負載係數this

  4. static final int TREEIFY_THRESHOLD = 8;
    桶內元素樹化的最少個數spa

  5. static final int UNTREEIFY_THRESHOLD = 6;
    反樹化時, 桶內元素的最多個數code

  6. final float loadFactor;
    設置的負載係數對象

  7. static final int MIN_TREEIFY_CAPACITY = 64;
    桶內元素就行樹化的時候,整個容器的須要達到的最小容量繼承

  8. transient int modCount;
    容器內部發生結構性改變的次數(用於iterator遍歷)

  9. transient int size;
    容器內部存儲的元素個數

  10. transient Node<K,V>[] table;
    存儲桶的數組,這個是HashMap的核心

  11. int threshold;
    進行再次內存分配的時候。元素須要達到的個數

  12. transient Set<Map.Entry<K,V>> entrySet; 提供map的訪問接口

####2、公有方法

  1. public void clear() 清空內部元素。

  2. public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
    獲取與key對應的value,而且將它們做爲參數調用remappingFunction(),獲得新的value。
    若是key原來不存在的話,將新的key,value添加進去,可是value不能爲空值。

  3. public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
    根據key去查找node. 分爲如下幾種狀況 :

  1. node不存在的話,就添加新的node,key爲key,value爲mappingFunction.apply(key),添加的時候可能會對bucket內元素進行treeify
  2. node存在,且值不爲空,直接返回
  3. node存在,值爲空,使用mappingFunction(key)進行計算,而後更新值
  1. public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
    根據key去查找node,若node存在,則使用mappingFunction(key)去更新值,不存在馬上返回。

  2. public boolean containsKey(Object key)
    根據key去查找node,調用內部的getNode方法

  3. public V put(K key, V value)
    將鍵值對放入bucket內部,此處注意可能要進行treeify。

  4. public boolean containsValue(Object value)
    遍歷table,而且遍歷每一個bucket

  5. public Set<Map.Entry<K,V>> entrySet()
    返回一個EntrySet類型對象,EntrySet類型繼承自AbstractSet,咱們主要用這個類來進行對HashMap的遍歷

  6. public void forEach(BiConsumer<? super K, ? super V> action)
    針對每個內部存儲的元素,調用action.accept()方法,在調用過程當中,modCount不能改變,這就是爲何HashMap的迭代是failFast的,若是改變就會拋出異常。

  7. public V get(Object key)
    獲取key對應的value

  8. public V getOrDefault(Object key, V defaultValue)
    JDK8新方法,若是value爲null,返回defaultValue。

  9. public boolean isEmpty()
    返回size == 0

  10. public Set<K> keySet()
    返回一個包含全部key的Set,能夠對這個set進行迭代,而後遍歷

  11. public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)
    經過key去查詢一個已存在的鍵值對,而且將oldValue與newValue傳遞給remappingFunction進行計算後從新賦值給對應key

  12. public V put(K key, V value)
    將鍵值對存入map內,涉及到resize以及treeify

  13. public void putAll(Map<? extends K, ? extends V> m)
    經過entrySet()遍歷m,將m內的全部元素插入新的map

  14. public V putIfAbsent(K key, V value)
    若是key對象的鍵值對不存在,則存入新的鍵值對

  15. public V remove(Object key)
    經過key去檢索鍵值對,而且刪除

  16. public boolean remove(Object key, Object value)
    經過key去檢索鍵值對,而且經過equals方法比較檢索出來的value,若value相等則移除

  17. public boolean replace(K key, V oldValue, V newValue)
    若是key對應的map內的value爲oldValue,則將其替換爲newValue

  18. public V replace(K key, V value)
    強key對象的值替換爲value

  19. public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
    對於全部的鍵值對調用function,apply()方法,並將返回值做爲新的value

  20. public int size()
    返回大小

  21. public Collection<V> values()
    獲取全部的值的Collection(可是並非說把全部的值存儲在了相似於List這樣的集合裏面了,咱們只是能夠調用Collection的接口而已)

####3、簡單實現

package com.braon.jdk;

public class SimpleHashMap<K, V> {
    private Node<K, V>[] table;
    private int capacity = 4;
    private float loadFactor = 0.75f;
    private int threshold = 3;
    private int size;

    public SimpleHashMap() {
        init();
    }

    public SimpleHashMap(int capacity, int loadFactor) {
        this.capacity = capacity;
        this.loadFactor = loadFactor;
        this.threshold = capacity * loadFactor;
        init();
    }

    @SuppressWarnings("unchecked")
    private void init() {
        capacity = 1 << 4;
        table = new Node[capacity];
        size = 0;
    }

    public void clear() {
        init();
    }

    public void put(K K, V V) {
        if (K == null || V == null) {
            return;
        }
        // space is narrow
        else if (size + 1 > threshold) {
            resize();
        }

        Node<K, V> newNode = new Node<>();
        newNode.setK(K);
        newNode.setV(V);
        newNode.setHash(K.hashCode() & (capacity - 1));
        newNode.setNext(null);

        if (putNode(newNode))
            size++;
    }

    public V get(K K) {
        // calculate store place
        int hash = K.hashCode() & (capacity - 1);
        Node<K, V> first = table[hash];

        if (first != null) {
            do {
                if (first.getK().equals(K))
                    return first.getV();
            } while ((first = first.next) != null);
            return null;
        } // if
        else
            return null;
    }

    @SuppressWarnings("unchecked")
    private void resize() {
        // recaculate the array size
        capacity = capacity << 1;
        threshold = (int) (loadFactor * capacity);

        // keep the old one, and renew a new table
        Node<K, V>[] oldTable = table;
        table = new Node[capacity];
        // relay the elements
        for (Node<K, V> first : oldTable) {
            while (first != null) {
                first.setHash(first.getK().hashCode() & (capacity - 1));
                this.putNode(first);

                // we need to remove the link
                Node<K, V> next = first.next;
                first.next = null;
                first = next;
            } // while
        } // for
    }

    public int getSize() {
        return size;
    }

    public int getCapacity() {
        return capacity;
    }

    private boolean putNode(Node<K, V> newNode) {
        Node<K, V> first = table[newNode.getHash()];
        if (first == null) {
            table[newNode.getHash()] = newNode;
        } else {
            while (first.next != null) {
                // two absolute equivalent K, we need to update the V
                if (first.getK().equals(newNode.getK())) {
                    first.setV(newNode.getV());
                    return false;
                }
                first = first.next;
            }

            if (first.getK().equals(newNode.getK())) {
                first.setV(newNode.getV());
                return false;
            } else
                first.next = newNode;
        }
        return true;
    }

    public void print() {
        System.out.println(toString() + "\r\n");
    }

    public void remove(K k) {
        if (k == null)
            return;

        int hash = k.hashCode() & (capacity - 1);
        Node<K, V> prev = table[hash];
        Node<K, V> cur = table[hash].next;

        if (prev.getK().equals(k)) {
            table[hash] = table[hash].next;
            size--;
            return;
        }
        while (cur != null) {
            if (cur.getK().equals(k)) {
                prev.next = cur.next;
                size--;
                return;
            }
            prev = cur;
            cur = cur.next;
        }
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        for (Node<K, V> node : table) {
            if (node != null) {
                do {
                    sb = sb.append("hashCode: " + node.getHash() + ", K: " + node.getK() + ", V: "
                            + node.getV() + "   ");
                } while ((node = node.next) != null);
                sb.append("\r\n");
            }
        }
        return sb.toString();
    }
}

class Node<K, V> {
    Node<K, V> next;
    private K k;
    private V v;
    private int hash;

    public Node() {
    }

    public Node(K k, V v) {
        this.k = k;
        this.v = v;
    }

    public int getHash() {
        return hash;
    }

    public void setHash(int hash) {
        this.hash = hash;
    }

    public K getK() {
        return k;
    }

    public void setK(K k) {
        this.k = k;
    }

    public Node<K, V> getNext() {
        return next;
    }

    public void setNext(Node<K, V> next) {
        this.next = next;
    }

    public V getV() {
        return v;
    }

    public void setV(V v) {
        this.v = v;
    }
}

####注意點 一、須要同時重載equals和hashCode方法 二、size增加後又減少,可是capacity不會減少 三、使用entrySet()進行遍歷,比較快,使用keySet()遍歷,相對較慢 四、hashCode方法的編寫須要注意,最好不要有多個值產生相同的hashCode,否則對查詢產生影響 五、loadfactor最好不要改變 六、查詢次數較多,插入次數相對較少,且元素hashCode相對集中,請使用TreeHashMap 七、values()方法獲取的返回值並無存儲了任何一個value的引用,只是咱們能夠用Collection接口的方法去調用這個返回值而已 八、耗時較多的插入通常都是由於須要resize或者treeify

相關文章
相關標籤/搜索