HashMap的初始容量 背景 很多人可以把HashMap的原理描述的很溜。比如JDK1.7之前,底層數據結構是數組+鏈表。JDK1.8之後,出於效率上的考慮,在數組長度大於64,鏈表長度大於8的時候,會轉換為紅黑樹。 甚至知道對於賦值了容量的都會做一個變成2的n次方的操作。它的hash方法為了防止 ...
HashMap的初始容量
背景
很多人可以把HashMap的原理描述的很溜。比如JDK1.7之前,底層數據結構是數組+鏈表。JDK1.8之後,出於效率上的考慮,在數組長度大於64,鏈表長度大於8的時候,會轉換為紅黑樹。
甚至知道對於賦值了容量的都會做一個變成2的n次方的操作。它的hash方法為了防止高位變化大或者低位變化大將它本身hash值右移16位和自身原hash值做一個按位異或操作再與容量-1做按位與。還知道預設的負載因數是0.75,這個值是經過概率論統計出來的,最好不要改。
瞭解的這麼清楚,我就想問一下為什麼從資料庫中取出來一個list,之後轉換成hashmap。直接用的是Map map = new HashMap()或者是Map map = Maps.newHashMap(),為什麼不賦初始容量呢?
分析
容量的大小會在put過程中發生resize操作。如果初始不賦值。預設容量是16。那比如從資料庫中取出來1000個元素。put過程中會從16->32->64->128……,運行多次resize操作。resize操作數組,需要將所有元素進行複製和rehash,效率是很低的。
所以也有一些同學考慮到這個問題,代碼是這麼寫的: Map map = new HashMap(list.size());
這個寫法也有問題,因為resize並不是到達容量上限才resize。為了儘量避免hash衝突,是超過閾值threshold就擴容。而這個threshold=容量*負載因數。
所以我更建議的寫法是Map map = new HashMap(list.size()/負載因數)。
這樣理論上可以比Map map = new HashMap(list.size())減少一次resize。
總結
在可以確定HashMap容量時,最好Map map = new HashMap(list.size()/負載因數)來初始化,避免自動擴容帶來的性能損耗。
思考
ConcurrentHashMap怎麼來更合理的初始化?
JVM記憶體結構和Java記憶體模型
背景
前段時間偶然看到有篇文章批判很多人對「JVM記憶體模型」這個概念不清楚,說這個經典的圖並不是記憶體模型而是記憶體結構。
而記憶體模型應該是JSR133規範里介紹的volatile、final和synchronized等關鍵字的記憶體語義。
分析
這個非常富有淘金式思維的作者卻搞混了一個概念,看看下麵JSR-133規範里是怎麼說的:JSR133規範里講的Java記憶體模型,並沒有說是JVM的記憶體模型啊。
Java記憶體模型講的是Java語言本身的規範,這個規範包含了各個Java標準關鍵字在JVM里是怎樣運作的。而JVM記憶體模型描述的是Java虛擬機怎樣運行位元組碼的。所以上面經典的圖說是JVM記憶體模型也不為過。不過根據官網,叫JVM記憶體結構更為標準。證據如下:
https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-2.html#jvms-2.5
在oracle官網裡,介紹了這個概念
總結
Java記憶體模型和JVM記憶體模型是兩個概念。