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

相关文章
相关标签/搜索