HashMap负载因子

概念 HashMap的底层存在着一个名字为table的Entry数组,在实例化HashMap的时候,会输入两个参数,一个是 int initCapacity(初始化数组大小,默认值是16),一个是float loadFactor(负载因子,默认值是0.75),首先会根据initCapacity计算出一个大于或者等于initCapacity且为2的幂的值capacity,例如initCapacity为15,那么capacity就会为16,还会算出一个临界值threshold,也就是capacity * loadFactor

/** * Constructs an empty HashMap with the specified initial * capacity and load factor. * * @paraminitialCapacity the initial capacity * @paramloadFactorthe 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) 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 int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; init(); }

initailCapacity,loadFactor会影响到HashMap扩容。
HashMap每次put操作是都会检查一遍 size(当前容量)>initailCapacity*loadFactor 是否成立。如果不成立则HashMap扩容为以前的两倍(数组扩成两倍),
然后重新计算每个元素在数组中的位置,然后再进行存储。这是一个十分消耗性能的操作。
所以如果能根据业务预估出HashMap的容量,应该在创建的时候指定容量,那么可以避免resize().
/** * Rehashes the contents of this map into a new array with a * larger capacity.This method is called automatically when the * number of keys in this map reaches its threshold. * * If current capacity is MAXIMUM_CAPACITY, this method does not * resize the map, but sets threshold to Integer.MAX_VALUE. * This has the effect of preventing future calls. * * @param newCapacity the new capacity, MUST be a power of two; *must be greater than current capacity unless current *capacity is MAXIMUM_CAPACITY (in which case value *is irrelevant). */ 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)(newCapacity * loadFactor); }

因为table数组的容量增加了,那么相应的table的length也增加了,那么之前存储的元素的位置也就不一样了,比如之前的length是16,现在的length是32,那么hashCode模16和HashCode模32的结果很有可能会不一样,所以就只有重新去计算新的位置,方法是遍历数组,在遍历数组上的entry链。
/** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable) { Entry[] src = https://www.it610.com/article/table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry e = src[j]; if (e != null) { src[j] = null; do { Entry next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } }

下面来看put方法,首先会根据key值计算出其HashCode值,然后在通过一个indexFor方法计算出此元素该存放于table数组的哪个数组之中,再检测此table的此坐标位置的entry链是否存在此key或者此key值,若存在,则更新此元素的value值。
/** * 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 key, or *null if there was no mapping for key. *(A null return can also indicate that the map *previously associated null with key.) */ 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 e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = https://www.it610.com/article/e.value; e.value = value; e.recordAccess(this); return oldValue; } }modCount++; addEntry(hash, key, value, i); return null; }

addEntry方法参数bucketIndex就是当前元素应该插入到entry数组的下标,先取出放在此位置的entry,然后把当前元素放入该数组中,当前元素的next指向之前取出元素,形成entry链表。(描述的不是很清楚,大概就是把新加入的entry当成头放入到数组当中,然后指向之前的链表),放入之后就去判断当前的size是否达到了threshold极限值,若达到了,将会进行扩容。
/** * Adds a new entry with the specified key, value and hash code to * the specified bucket.It is the responsibility of this * method to resize the table if appropriate. * * Subclass overrides this to alter the behavior of put method. */ void addEntry(int hash, K key, V value, int bucketIndex) { Entry e = table[bucketIndex]; table[bucketIndex] = new Entry(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); }

总结 【HashMap负载因子】负载因子表示一个散列表的空间的使用程度,有这样一个公式:initailCapacity*loadFactor=HashMap的容量。
所以负载因子越大则散列表的装填程度越高,也就是能容纳更多的元素,元素多了,链表大了,所以此时索引效率就会降低。
反之,负载因子越小则链表中的数据量就越稀疏,此时会对空间造成烂费,但是此时索引效率高。

    推荐阅读