HashMap源码解析

HashMap源码:

首先看看HashMap到底是一个什么样的类,它继承了哪些类。

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

由上面HashMap头部可以看到,HashMap是AbstractMap的子类、Map的子接口,并且实现了其他的一些额外的东西,Cloneable和Serializable,我们先看看AbstractMap的部分源码。

package asdf;

import java.util.*;

/**
 * Created by ping.miao on 2015/8/3.
 */
public abstract class AbstractMap<K,V> implements Map<K,V> {
    /**
     * 构造方法
     */
    protected AbstractMap() {
    }

    /**
     * 得到当前map的长度
     */
    public int size() {
        return entrySet().size();
    }

    /**
     * 判断当前map是否为空
     * 假如当前map的长度为0是那么该map为空,返回true
     */
    public boolean isEmpty() {
        return size() == 0;
    }

    /**
     * 判断map是否包含一个或者多个key所对应的value等于参数中给出的value
     * 及判断满足条件(value == null ? v == null : value.equals(v))的v
     * 是否存在,存在返回true,不存在返回false
     */
    public boolean containsValue(Object value) {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        if (value==null) {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (e.getValue()==null)
                    return true;
            }
        } else {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (value.equals(e.getValue()))
                    return true;
            }
        }
        return false;
    }

    /**
     * 判断map中是否存在一个key值与参数给出的key值相等
     * 及判断满足条件(key == null ? k == null : key.equals(k))的k
     * 是否存在,存在返回true,不存在返回false
     */
    public boolean containsKey(Object key) {
        Iterator<Map.Entry<K,V>> i = entrySet().iterator();
        if (key==null) {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (e.getKey()==null)
                    return true;
            }
        } else {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (key.equals(e.getKey()))
                    return true;
            }
        }
        return false;
    }

    /**
     * 根据当前key值得到对应的value
     */
    public V get(Object key) {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        if (key==null) {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (e.getKey()==null)
                    return e.getValue();
            }
        } else {
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (key.equals(e.getKey()))
                    return e.getValue();
            }
        }
        return null;
    }
    public V put(K key, V value) {
        throw new UnsupportedOperationException();
    }

    /**
     * 删除map中的键值对(key,value),并返回未删除前key所对应的value
     */
    public V remove(Object key) {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        Entry<K,V> correctEntry = null;
        if (key==null) {
            while (correctEntry==null && i.hasNext()) {
                Entry<K,V> e = i.next();
                if (e.getKey()==null)
                    correctEntry = e;
            }
        } else {
            while (correctEntry==null && i.hasNext()) {
                Entry<K,V> e = i.next();
                if (key.equals(e.getKey()))
                    correctEntry = e;
            }
        }

        V oldValue = null;
        if (correctEntry !=null) {
            oldValue = correctEntry.getValue();
            i.remove();
        }
        return oldValue;
    }
    
    public void putAll(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }
    
    public void clear() {
        entrySet().clear();
    }

    transient volatile Set<K>        keySet = null;
    transient volatile Collection<V> values = null;

    public Set<K> keySet() {
        if (keySet == null) {
            keySet = new AbstractSet<K>() {
                public Iterator<K> iterator() {
                    return new Iterator<K>() {
                        private Iterator<Entry<K,V>> i = entrySet().iterator();

                        public boolean hasNext() {
                            return i.hasNext();
                        }

                        public K next() {
                            return i.next().getKey();
                        }

                        public void remove() {
                            i.remove();
                        }
                    };
                }

                public int size() {
                    return AbstractMap.this.size();
                }

                public boolean isEmpty() {
                    return AbstractMap.this.isEmpty();
                }

                public void clear() {
                    AbstractMap.this.clear();
                }

                public boolean contains(Object k) {
                    return AbstractMap.this.containsKey(k);
                }
            };
        }
        return keySet;
    }

    public Collection<V> values() {
        if (values == null) {
            values = new AbstractCollection<V>() {
                public Iterator<V> iterator() {
                    return new Iterator<V>() {
                        private Iterator<Map.Entry<K,V>> i = entrySet().iterator();

                        public boolean hasNext() {
                            return i.hasNext();
                        }

                        public V next() {
                            return i.next().getValue();
                        }

                        public void remove() {
                            i.remove();
                        }
                    };
                }

                public int size() {
                    return AbstractMap.this.size();
                }

                public boolean isEmpty() {
                    return AbstractMap.this.isEmpty();
                }

                public void clear() {
                    AbstractMap.this.clear();
                }

                public boolean contains(Object v) {
                    return AbstractMap.this.containsValue(v);
                }
            };
        }
        return values;
    }

    public abstract Set<Entry<K,V>> entrySet();

    /**
     * 判断map与o是否相等,相等返回true,不相等返回false
     */
    public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Map))
            return false;
        Map<K,V> m = (Map<K,V>) o;
        if (m.size() != size())
            return false;

        try {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

    /**
     * 计算Map的hashCode,它的值为map的entrySet中每一个元素的hashcode的和
     */
    public int hashCode() {
        int h = 0;
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext())
            h += i.next().hashCode();
        return h;
    }
}

底层实现:

static final Entry<?,?>[] EMPTY_TABLE = {};
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;


构造方法:

/**
     * 无参构造方法,使用默认容量16,填装因子0.75,构造一个空的HashMap
     */
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    /**
     * 以指定的initialCapacity和loadFactor构造一个空的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);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

    /**
     * 以指定的initialCapacity,填装因子0.75构造一个空的HashMap
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * 构造一个包含指定的Map中的元素的HashMap
     * 使用默认的填装因子0.75和可以包含Map中所有元素的初始容量来创建一个新的HashMap
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        inflateTable(threshold);

        putAllForCreate(m);
    }


由上面的底层实现及构造方法可以看出,当程序初始化HashMap的时候,会创建一个长度为capacity的Entry数组,这个数组里面可以存储元素的位置被称作为“桶(bucket)”,每个bucket都有其指定的索引,程序可以根据其索引快速访问该bucket里存储的元素。

需要注意的是:无论何时,HashMap的每个桶只能存储一个元素也就是一个Entry,由于Entry对象可以包含一个引用变量指向下一个Entry,那么这样可以出现的情况就是:HashMap的bucket中只有一个Entry,这个Entry指向另一个Entry,这样就形成了一个Entry链,它的结构图如下所示:



数据的插入(着重分析put(K key, V value)):

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        //如果 key 为 null,调用 putForNullKey 方法进行处理
        if (key == null)
            return putForNullKey(value);
        // 根据 key 的 keyCode 计算 Hash 值
        int hash = hash(key);
        // 搜索指定 hash 值在对应 table 中的索引
        int i = indexFor(hash, table.length);
        // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            // 找到指定 key 与需要放入的 key 相等(hash 值相同
            // 通过 equals 比较放回 true)
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        // 如果 i 索引处的 Entry 为 null,表明此处还没有 Entry
        modCount++;
        // 将 key、value 添加到 i 索引处
        addEntry(hash, key, value, i);
        return null;
    }

从上面的代码可以看出:当HashMap存储键值对时,没有考虑过当前Entry中的value,仅仅是根据key值来计算并决定每个Entry的存储位置。这说明了一个事情那就是:我们可以把Map中的value当成是key的附属,当key的位置被决定后,value的值也随之保存在那个位置就可以了。

HsahMap存储键值对时,根据hashCode()来计算哈希码,这个方法是一个纯粹的数学计算,源码如下:

final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

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

我们可能都知道,在HashMap中,对于任意给定的对象,只要它的hashCode()返回值相同,那么程序调用hash(int h)方法所计算的到的哈希码值是相同的,这时候,出现这种情况就叫Hash表中出现了冲突,怎么解决这个冲突呢。程序中是这样来解决这个冲突的,通过调用indexFor(int h, int length) 方法来计算该对象应该保存在table数组的哪一个索引处。indexFor(int h, int length) 方法的源码如下:

static int indexFor(int h, int length) {
        return h & (length-1);
    }

这个方法非常巧妙,它总是通过h&(table.length-1)来得到对象的保存位置,而HashMap的底层数组长度总是2^n。那么,当length总是2的倍数的时候,h&(length-1)将是一个非常巧妙的设计:假设当前h=5,length=16,那么h&(length-1)将得到5;当h=6,length=16,h&(length-1)将得到6,这样依次,当h=15时,h&(length-1)将得到15;当h=16时,h&(length-1)将得到0,;当h=17时,h&(length-1)将得到1。就这样,它所得到的索引值总是在table数组的索引内。

根据上面的put方法的源码可以看出,当程序试图将一个键值对放入HashMap中,程序首先根据key值的哈希码决定Entry的存储位置,如果两个Entry的key值的哈希码相同,那么它们的存储位置应该是相同的。如果这两个Entry的key相同,新添加的Entry的value将会覆盖集合中原有的Entry的value。如果两个Entry的key不相容的话,新添加的Entry降雨集合中原有的Entry形成Entry链(开链法解决冲突),并且新添加的Entry位于Entry链的头部。具体的实现是在addEntry方法中实现的,我们来看看addEntry的源码:

void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

从上面的代码可以看到:HashMap总是将新添加的Entry对象放入table数组的bucketIndex索引处,假如bucketIndex索引处已经有一个Entry对象,那么新添加Entry对象指向原有的Entry对象,这样就产生一个Entry链,假如bucketIndex索引处没有Entry对象,放入新的Entry对象。

HashMap数据读取:

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }
final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        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;
    }

从上面的代码可以看出,HashMap读取它的某个键值对时,先要计算出key的哈希码,再根据哈希码找出key在table上的索引,根据索引取出Entry,最后返回key值对应的value即可。这时候你可能就会想到,假如HashMap的每个bucket只有一个Entry时,那么HashMap就可以很快的根据索引取出该bucket里的Entry,时间复杂度可以达到o(1),但是当出现冲突时,这里是采用开链法来处理冲突,这时候一个bucket可能对应对一个Entry链,刚好待读出的数据就在链末尾,这时候时间复杂度就不能为o(1)了,它的时间复杂度为o(n)。

另外,需要注意的是,创建HashMap时有一个默认的填装因子,默认值为0.75,这个是时间和空间成本上的一个折中,增大这个填装因子可以减少hash表所占用的内存空间,但是这样会增加查询数据的时间开销,而查询又是最频繁的操作;减少负载因子呢会提高数据查询的性能,但是会增加hash表所占用的空间。在使用的时候还是要以实际情况而定,一般都选择填装因子的默认值。

上面的内容已经将HashMap的原理及实现差不多介绍完了,到此时还剩下一个东西,那就是HashMap的扩容,如果你开始就知道HashMap会保存很多的键值对,那么就可以在创建时使用较大的初始化容量,假如HashMap中的Entry的数量一直都不会超过最大的容量,那么就无需调用resize方法来进行扩容,那么我们来看看resize方法源码是怎么实现的:

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, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值