HashMap的实现方式

当程序执行

Map map = new HashMap();

的时候,会先执行HashMap的默认无参数构造方法

public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

这里的DEFAULT_INITIAL_CAPACITY是指新建HashMap的初始容量大小,默认是static final int DEFAULT_INITIAL_CAPACITY = 1 << 4(也就是16)。DEFAULT_LOAD_FACTOR是指负载因子,也就是这个容量装满到什么程度,默认是static final float DEFAULT_LOAD_FACTOR = 0.75f。然后程序就会执行以下程序进行new HashMap的初始化:

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)  // 如果初始容量小于0,则异常
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)   // 如果初始容量大于极限(2的30次方),则异常
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))   // 负载因子小于0或是空,则异常
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

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

注意,在HashMap类里面,有以下两个声明:

    /**
     * An empty table instance to share when the table is not inflated.
     */
    static final Entry<?,?>[] EMPTY_TABLE = {};

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

这是定义了一个Entry的空数组。当我们使用put方法,将元素放进HashMap里的时候,程序执行以下代码:

    /**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);  // 当第一次执行put方法的时候,这个Entry数组是空的,那么就以上述提到的HashMap的初始容量大小来定义这个Entry数组的长度
        }                             
        if (key == null)
            return putForNullKey(value);  // 如果key是null,则以hash为0,将这个key-value组成的Entry存到数组
        int hash = hash(key);             // 如果Key不为0,则计算其hash
        int i = indexFor(hash, table.length);   // 根据计算得到的hash和Entry数组的长度,计算这个key-value组
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {  //成的Entry应该存入数组的哪一个下标位置上
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;   // 遍历数组当前已经存在的元素,如果数组在当前下标上已经存放了一个
                e.value = value;        // Entry,则用equals比较key,如果equals返回true,则新的value替换旧的
                e.recordAccess(this);
                return oldValue;      // 返回旧的value
            }
        }

        modCount++;
        addEntry(hash, key, value, i);  // 如果equals返回false,则新的Entry也会存放在数组在这个下标位置上
        return null;
    }

 上述说到,如果新添加的key-value组成的Entry跟已经存在的key-value组成的Entry的hash一样,而且通过equals比较两者的Key返回的是false。那么,它们都会被存放在上述table这个Entry数组相同位置上,这两者会形成链表结构,新增的在表头,先加的在表尾。
  所以总的来说,HashMap其实就是一个数组,如果没有指定初始容量大小,则这个数组初始长度就是16,数组中每个元素都是Entry组成的链表结构。新增key-value到HashMap时,先通过key计算出其hash值,通过hash值得到这对key-value应该存入数组中的哪个位置(也就是数组的下标),如果该下标的位置上已经存放了一对或多对key-value,则遍历并通过equals方法比较key是否返回true,如果是,则替换;如果否,则新的key-value(就是一个Entry)跟该下标的位置上已经存放Entry组成链表结构,新增的在链头。
  由于key-value存放在数组中的下标是通过key的hash值得到的,所以HashMap并不保证顺序。而且上述提到一个叫负载因子的东西,新HashMap初始化时默认是0.75,也就是说,当这个HashMap储存元素个数达到了总容量(也就是数组的长度)的0.75时,HashMap就是自动扩容为原容量的2倍。就是默认情况下,HashMap储存元素个数达到了16*0.75=12时,HashMap的容量大小会扩展成16*2=32。然后重新计算每个元素在数组中的位置。所以如果我们使用HashMap时预知到存放键值对的个数时,给定初始容量大小,就可以避免扩容造成的性能消耗。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值