修改公司老代碼的時(shí)候,發(fā)現(xiàn)阿里編碼規(guī)約插件提示HashMap初始化時(shí)盡量指定初始值大小,因?yàn)樵O(shè)置合理的初始值可以提升性能:
?HashMap繼承自AbstractMap類,實(shí)現(xiàn)了Map、Cloneable、java.io.Serializable接口,是基于散列表實(shí)現(xiàn)的雙列集合,它存儲(chǔ)的是key-value鍵值對(duì)映射,每個(gè)key-value鍵值對(duì)也被稱為一條Entry條目。其中的 key與 value,可以是任意的數(shù)據(jù)類型,其類型可以相同也可以不同。但一般情況下,key都是String類型,有時(shí)候也可以使用Integer類型;value可以是任何類型。并且在HashMap中,最多只能有一個(gè)記錄的key為null,但可以有多個(gè)value的值為null。HashMap中這些鍵值對(duì)(Entry)會(huì)分散存儲(chǔ)在一個(gè)數(shù)組當(dāng)中,這個(gè)數(shù)組就是HashMap的主體。
HashMap的實(shí)例有兩個(gè)影響其性能的參數(shù):初始容量和裝載因子。容量是哈希表中的桶數(shù),初始容量就是創(chuàng)建哈希表時(shí)的容量。負(fù)載因子是一種度量方法,用來衡量在自動(dòng)增加哈希表的容量之前,哈希表允許達(dá)到的滿度。當(dāng)哈希表中的條目數(shù)超過負(fù)載因子和當(dāng)前容量的乘積時(shí),哈希表將被重新哈希(即重新構(gòu)建內(nèi)部數(shù)據(jù)結(jié)構(gòu)),這樣哈希表的桶數(shù)大約是原來的兩倍。
那么在使用HashMap時(shí)要求盡量指定初始值,該指定多少合適?(可以直接跳到后面看結(jié)論)
一般來說,初始值大小的設(shè)定應(yīng)該根據(jù)實(shí)際需要進(jìn)行設(shè)置。另外,也建議將初始值大小設(shè)置為2的冪次方,這樣可以更好地利用HashMap的內(nèi)部機(jī)制,提高程序的運(yùn)行效率。
如果不設(shè)置,默認(rèn)值是16。
如我截圖的代碼,就存放2個(gè)變量,初始值設(shè)置2,但是設(shè)置2真的合理嗎?
我們可以看一下HashMap的源碼,看看它里面是怎么實(shí)現(xiàn)的。
不設(shè)置初始值的構(gòu)造方法:
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
設(shè)置初始值的構(gòu)造方法:
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
this方法的源碼:
/**
* 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)
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;
this.threshold = tableSizeFor(initialCapacity);
}
里面的變量和方法,,我加了一些翻譯說明:
/**
* The next size value at which to resize (capacity * load factor).
* 翻譯:用于調(diào)整大小的下一次大小值(容量*負(fù)載因子)
* @serial
*/
// (The javadoc description is true upon serialization.
// Additionally, if the table array has not been allocated, this
// field holds the initial array capacity, or zero signifying
// DEFAULT_INITIAL_CAPACITY.)
int threshold;
//默認(rèn)大小為16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* Returns a power of two size for the given target capacity.(翻譯:返回給定目標(biāo)容量的大小為2的冪)
* 這段代碼是一個(gè)名為 tableSizeFor 的靜態(tài)方法,它接受一個(gè)整數(shù)參數(shù) cap,并返回一個(gè)整數(shù)值。該方法的作用是計(jì)算并返回一個(gè)用于散列表(hash table)的合適的大小。
*
* 具體來說,該方法首先將 cap 減去 1,然后使用位運(yùn)算符將結(jié)果向右移動(dòng),并按位或運(yùn)算,以清除最低位的 1。
* 這個(gè)過程會(huì)重復(fù) 5 次,每次向右移動(dòng)的位數(shù)為 1、2、4、8 和 16。最后,如果計(jì)算出的值小于 0,則返回 1;
* 如果計(jì)算出的值大于等于 MAXIMUM_CAPACITY,則返回 MAXIMUM_CAPACITY;否則返回計(jì)算出的值加 1。
*
* 這個(gè)方法的設(shè)計(jì)目的是為了在散列表中獲得更好的負(fù)載因子(load factor),從而減少哈希沖突(hash collision)的概率。
* 負(fù)載因子是指散列表中元素的數(shù)量與散列表大小的比率。較小的負(fù)載因子意味著每個(gè)桶(bucket)中存儲(chǔ)的元素較少,從而減少了哈希沖突的概率。
* 但是,較小的負(fù)載因子也會(huì)導(dǎo)致散列表的空間利用率較低。因此,選擇合適的散列表大小非常重要。
* @param cap
* @return
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
上面的源碼可以看出threshold是個(gè)閾值,它的計(jì)算是根據(jù)tableSizeFor方法來的(根據(jù)設(shè)置的初始值計(jì)算的),到達(dá)這個(gè)閾值就會(huì)觸發(fā)擴(kuò)容,擴(kuò)容是會(huì)影響性能的。
再看一下putMapEntries方法的源碼,看注釋的意思是實(shí)現(xiàn)了Map.putAll和Map構(gòu)造函數(shù):
/**
* Implements Map.putAll and Map constructor.
*
* @param m the map
* @param evict false when initially constructing this map, else
* true (relayed to method afterNodeInsertion).
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
else if (s > threshold)
resize();
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
這里面“?float ft = ((float)s / loadFactor) + 1.0F;”就是我們初始值的大小計(jì)算方法。
有興趣的可以看下HashMap源碼研究一下。
結(jié)論:默認(rèn)不設(shè)置初始值大小,HashMap使用的是默認(rèn)大小,值為16,如果要指定大小可以根據(jù)“存儲(chǔ)數(shù)據(jù)的個(gè)數(shù)/0.75+1”計(jì)算出的結(jié)果作為初始值。
最后說明一下上面我的截圖代碼的問題,設(shè)置2是否合理?答案是不合理,根據(jù)計(jì)算公式得出,設(shè)置3是比較合理的。
原因是設(shè)置為2,剛好會(huì)觸發(fā)擴(kuò)容,造成完全沒有必要的性能浪費(fèi)。
參考以下代碼,設(shè)置為2:
Map<String, String> map = new HashMap<>(2);
map.put("startTime", "");
getMapLength(map);
map.put("endTime", "");
getMapLength(map);
結(jié)果為:說明進(jìn)行了擴(kuò)容
設(shè)置為3:
Map<String, String> map = new HashMap<>(3);
map.put("startTime", "");
getMapLength(map);
map.put("endTime", "");
getMapLength(map);
結(jié)果為:沒有進(jìn)行擴(kuò)容
打印Length方法:文章來源:http://www.zghlxwxcb.cn/news/detail-704131.html
/**
* 打印Length
* @param map
* @throws Exception
*/
static void getMapLength(Map<String, String> map) throws Exception {
Field field = HashMap.class.getDeclaredField("table");
field.setAccessible(true);
Object[] elementData = (Object[]) field.get(map);
System.out.println(elementData == null ? 0 : elementData.length);
}
PS:關(guān)于是否設(shè)置初始值影響性能、初始值設(shè)置的合理與否影響性能,可以通過測(cè)試代碼測(cè)一下,看看初始化的時(shí)間間隔。文章來源地址http://www.zghlxwxcb.cn/news/detail-704131.html
到了這里,關(guān)于Java HashMap初始化大小設(shè)置多少合適的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!