hashmap的put方法源码分析

put主源码如下:  

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
  
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

1.1、putForNullKey():当key为null的时候自动转向putForNullKey()方法,用来处理null键,将他们放到table[0]的位置,下面的是putForNullKey()方法,进来遍历entry链表,如果遍历后,发现没有key没为nulll存在,则直接添加一个entry,键为null,值为新的value;而当遍历后,发现key有null的时候,就返回就值,让新值覆盖旧值。

    private V putForNullKey(V value) {
            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;
        }

1.2当传入的键不为null的时候,先hash一下key的hashcode值(用来避免重写hashcode方法的不完美,保证hash散布的均匀),然后使用indexFor方法来找到对应在table[]上的位置

  indexFor源码:它没有对hash表的长度取余而使用了位运算来得到索引,这是为什么呢?因为length在hashmap中默认为2的幂次方,所以length-1所得到的二进制都是1构成的,所以hash和这个length-1做与运算其实也是对hash表的长度取余。位运算快于四则运算。(这也是为什么要去size为2的幂次方的原因,因为如果取其他数值,hash碰撞几率增大,可能size的二进制某一位上是0,导致好几个table位置无法存放数据,造成空间浪费)。

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

1.3、找到新元素的table[index]后,就遍历该位置上的entry链;如果仅仅使用equals进行链上比较效率会很低,所以我们先使用hash来比较过滤。遇到相同的值就覆盖,返回旧值。如果没有相同值就直接addentry()进行头插法插入链表。

1.4、增加entry方法addentry():addentry的源码:判断当前table的size大于等于边界值并且index位置上不为空,就2倍扩容table他的大小,然后再根据传入的hash,key,value重新定向这个元素的位置。创建entry并创建。

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

1.4扩容resize(扩容条件,当前容量大于边界值,并且添加的index对应的table上不为null)

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

1.5、关键是空表扩容后的拷贝到新表的方法transfer方法,下面是源码:将table上的entry进行遍历,传入的rehash用来判断是否用来重新hash,为true的话就判断key是否为null,如果为null就hash值为0,不为null就重hash,然后重定位index,然后头插法放到对应的位置上

 void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

1.6、最后进行createEntry(),头插法插入,最后将e传入新table[]中,表示指针指向旧值。

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++;
    }

至此全部完成

猜你喜欢

转载自www.cnblogs.com/television/p/9436588.html