HashMap代码解析

Map

先看一下jdk中map的定义

public interface Map<K,V>{
    int size();
    boolean isEmpty();
    boolean containsValue(Object value);
    V get(Object key);
    V put(K key, V value);
    void putAll(Map<? extends K, ? extends V>m);
    void clear();
    Set<K> keySet();
    Collection<V> values();
    Set<Map.Entry<K, V>> entrySet();
    interface Entry<K, V>{
        V getValue();
        V setValue(V value);
        boolean equals(Object o);
        int hashCode();
    }
    boolean equals(Object o);
    int hashCode();
}

可以看到Map并没有实现Collection接口,因为它可以保存两个属性值key-value,同时还有增删该查等基本操作,同时可以看见Map中还定义了一个用来表示键值对K-V的接口Entry。

HashMap的实现

HashMap的定义

public class HashMap<K,V> extends AbstractMap<K,v> implements Map<K, V>, Cloneable, Serializable

HashMap是继承了AbstractMap抽象类,实现了Map,Cloneable,Serializable接口。

底层存储


// 默认初始容量为16,必须为2的n次幂
    static final int DEFAULT_INITIAL_CAPACITY = 16;

    // 最大容量为2的30次方
    static final int MAXIMUM_CAPACITY = 1 << 30;

    // 默认加载因子为0.75f
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    // Entry数组,长度必须为2的n次幂
    transient Entry[] table;

    // 已存储元素的数量
    transient int size ;

    // 下次扩容的临界值,size>=threshold就会扩容,threshold等于capacity*load factor
    int threshold;

    // 加载因子
    final float loadFactor ;
可以看出HashMap底层使用Entry数组存储数据。同时定义了加载因子等参数。
Entry的定义
static class Entry<K, V> implements Map.Entry<K, V>{
    final K key;//定义键值
    V value; 定义值
    Entry<K, V> next; //指向下一个节点
    final int hash;
    Entry(int h, K k, V v, Entry<K, V> n){
        value = v;
        next = n;
        key = k;
        hash = h;
    }

    public final K getKey(){
        return key;
    }

    public final V getValue(){
        return value;
    }

    public final V setValue(V newValue){
        V oldValue = value;
        value = newValue;
        return oldValue;
    }
      public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            return (key ==null   ? 0 : key.hashCode()) ^
                   ( value==null ? 0 : value.hashCode());
        }

        public final String toString() {
            return getKey() + "=" + getValue();
        }

        // 当向HashMap中添加元素的时候调用这个方法,这里没有实现是供子类回调用
        void recordAccess(HashMap<K,V> m) {
        }

        // 当从HashMap中删除元素的时候调动这个方法 ,这里没有实现是供子类回调用
        void recordRemoval(HashMap<K,V> m) {
        }
}

Entry是HashMap的内部类,它继承了Map的Entry接口,它定义了键值和下一个节点的引用以及hash值。可以很明确的看出来Entry是什么结构,它是单链表的一个节点。也就是说HashMap的底层结构是一个数组,而且数组的元素是一个单向链表
HashMap将所有的相同的散列值存储在一个链表中,也就是在一个链表内的元素的散列值是相同的。

构造方法

public HashMap<int initialCapacity, float loadFactor>{
       // 初始容量和加载因子合法校验
        if (initialCapacity < 0)
            throw new IllegalArgumentException( "Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException( "Illegal load factor: " +
                                               loadFactor);

        // Find a power of 2 >= initialCapacity
        // 确保容量为2的n次幂,是capacity为大于initialCapacity的最小的2的n次幂
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        // 赋值加载因子
        this.loadFactor = loadFactor;
        // 赋值扩容临界值
        threshold = (int)(capacity * loadFactor);
        // 初始化hash表
        table = new Entry[capacity];
        init();
}
/*
    构造一个使用默认容器变量(16)和默认加载因子的HashMap
*/
public HashMap(){
    this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
}

增加方法

public V put(K key, V value){
    //如果key为null,调用方法处理
    if(key == null)
     return putForNullKey(value);
     //使用key的hashCode计算相对应的hash值
     int hash = hash(key.hashCode());
     //通过hash值找到在数组中的位置
     int i = indexFor(hash, table.length);
     //取出index位置的链表,遍历链表查看是否存在相同的key
     for(Entry<K,V> e = table[i]; e != null ; e = e.next){
        Object k;
        //通过对比hash值,key判断是否已经存在相同的key
        if(e.hash == hash && ((k = e.key) == key || key.equals(k))){
            V oldValue = e.value;
            //用新的value替代之前旧的value
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    //若不存在相同的key
    modCount++;
    //在数组的第i个位置处添加一个新的节点
    addEntry(hash, key, value, i);
    //没有相同的key返回null
    return null;
}
private V putForNullKey(V value){
    //找到一个key为null的节点,覆盖,若没有,则新增一个
    for(Entry<K,V> e = table[0]; e != null; e = e.next){
        if(e.key == null){
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(0, null, value, 0);
    return null;
}

增加和我们上面分析的一样,通过key做hash取得一个散列值,将散列值对应到数组下标,然后k-v组成链表节点添加到数组。

计算hash的方法和计算索引的方法

/**
     * Applies a supplemental hash function to a given hashCode, which
     * defends against poor quality hash functions.  This is critical
     * because HashMap uses power -of- two length hash tables, that
     * otherwise encounter collisions for hashCodes that do not differ
     * in lower bits. Note: Null keys always map to hash 0, thus index 0.
     */
    static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
}

增加节点的方法

/*
    增加一个k-v,hash组成的节点在数组内,可能要对数组进行扩容
*/
    void addEntry(int hash, K key, V value, int bucketIndex){
        //下面两行代码的含义是创建一个新的节点放在链表的首部,旧节点后移
        Entry<K, V> e = table[bucketIndex];
        //创建一个节点,并且添加在首部
        table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
        //如果当前的HashMap的值已经达到了临界值,
    //  那么容量扩大两倍,并且将size计数+1
        if(size++ >= threshold)
            resize(2*table.length);
    }

新节点一直插入到最前端,一直是链表的头结点
扩容的方法:

    void resize(int newCapacity) {
        //当前数组
        Entry[] oldTable = table;
        //当前数组容量
        int oldCapacity = oldTable.length;
        //当前数组容已经是最大值,修改临界值
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        //创建一个新的数组链表
        Entry[] newTable = new Entry[newCapacity];
        //将当前数组元素移动到新数组中
        transfer(newTable);
        //将当前数组指向新创建的数组
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

void transfer(Entry[] newTable){
    //当前数组
    Entry[] src = table;
    //新数组长度
    int newCapacity = newTable.length;
    // 遍历当前数组的元素,重新计算每个元素所在数组位置
        for (int j = 0; j < src. length; j++) {
            // 取出数组中的链表第一个节点
            Entry<K,V> e = src[j];
            if (e != null) {
                // 将旧链表位置置空
                src[j] = null;
                // 循环链表,挨个将每个节点插入到新的数组位置中
                do {
                    // 取出链表中的当前节点的下一个节点
                    Entry<K,V> next = e. next;
                    // 重新计算该链表在数组中的索引位置
                    int i = indexFor(e. hash, newCapacity);
                    // 将下一个节点指向newTable[i]
                    e. next = newTable[i];
                    // 将当前节点放置在newTable[i]位置
                    newTable[i] = e;
                    // 下一次循环
                    e = next;
                } while (e != null);
            }
        }
}

删除

public V remove(Object key)
{
    Entry<K, V> = removeEntryForKey(key);
    return (e == null?null:e.va;ue);
}
/*
    根绝key删除链表节点
*/
final Entry<K.V> removeEntryForKey(Object key){
    //计算key的hash值
    int hash = (key == null) ? 0 : hash(key.hashCode());
     int i = indexFor(hash, table.length );
        // 找到该索引出的第一个节点
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        //遍历链表
        while(e != null){
            Entry<K,V> next = e.next;
            Object k;
            //如果hash值和key值都相等,则删除
            if(e.hash == hash &&
            (k = e.key) == key || (key != null && key.equals(k))){
                modCount++;
                size--;
                //若第一个节点要删除的节点
                if(pre == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;//返回被删除的节点
            }
            //保存当前节点为下次循环的上一个节点
            prev = e;
            //下次循环
            e = next;
        }
}

查找

public V get(Object key)
{
    //若key == null
    if(key == null)
        return getForNUllkey();
    //计算key对应的hash值
    int hash = hash(key.hashCode());
    //通过hash值找到key对应数组的索引位置,遍历该数组的链表
    for(Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next){
        Object k;
        //如果hash值和key都相等,则认为相等
        if(e.hash == hash && 
        (k = e.key) == key || key.equals(k)){
            return e.vaule;
        }
        return null;
    }
      private V getForNullKey() {
        // 遍历数组第一个位置处的链表
        for (Entry<K,V> e = table [0]; e != null; e = e. next) {
            if (e.key == null)
                return e.value ;
        }
        return null;
}

从删除和查找知道,我们只需要遍历hash值一样的数据,这样比全遍历效率高很多。

是否包含键

/**
     * Returns <tt>true</tt> if this map contains a mapping for the
     * specified key.
     *
     * @param   key   The key whose presence in this map is to be tested
     * @return <tt> true</tt> if this map contains a mapping for the specified
     * key.
     */
    public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }

    /**
     * Returns the entry associated with the specified key in the
     * HashMap.  Returns null if the HashMap contains no mapping
     * for the key.
     */
    final Entry<K,V> getEntry(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        for (Entry<K,V> e = table [indexFor (hash, table .length)];
             e != null;
             e = e. next) {
            Object k;
            if (e.hash == hash &&
                ((k = e. key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
}

是否包含值

/**
     * Returns <tt>true</tt> if this map maps one or more keys to the
     * specified value.
     *
     * @param value value whose presence in this map is to be tested
     * @return <tt> true</tt> if this map maps one or more keys to the
     *         specified value
     */
    public boolean containsValue(Object value) {
        if (value == null)
            return containsNullValue();

       Entry[] tab = table;
       // 遍历整个table查询是否有相同的value值
        for (int i = 0; i < tab. length ; i++)
            // 遍历数组的每个链表
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (value.equals(e.value ))
                    return true;
        return false;
    }

    /**
     * Special -case code for containsValue with null argument
     */
    private boolean containsNullValue() {
       Entry[] tab = table;
        for (int i = 0; i < tab. length ; i++)
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (e.value == null)
                    return true;
        return false;
}

容量检查

/**
     * Returns the number of key -value mappings in this map.
     *
     * @return the number of key- value mappings in this map
     */
    public int size() {
        return size ;
    }

    /**
     * Returns <tt>true</tt> if this map contains no key -value mappings.
     *
     * @return <tt> true</tt> if this map contains no key -value mappings
     */
    public boolean isEmpty() {
        return size == 0;
}

猜你喜欢

转载自blog.csdn.net/buzhbuzh/article/details/77429438
今日推荐