[改善Java代码]减少HashMap中元素的数量
在系統(tǒng)開發(fā)中我們經(jīng)常會使用HashMap作為數(shù)據(jù)集容器,或者是用緩沖池來處理,一般很穩(wěn)定,但偶爾也會出現(xiàn)內(nèi)存溢出的問題(OutOfMemory錯誤),而且這經(jīng)常是與HashMap有關(guān)的.而且這經(jīng)常是與HashMap有關(guān)的.比如我們使用緩沖池操作數(shù)據(jù)時,大批量的增刪改產(chǎn)操作就可能會讓內(nèi)存溢出,下面建立一段模擬程序,重現(xiàn)該問題,看代碼:
1 import java.util.HashMap; 2 import java.util.Map; 3 4 public class Client { 5 public static void main(String[] args) { 6 Map<String, String> map = new HashMap<String, String>(); 7 final Runtime rt = Runtime.getRuntime(); 8 // JVM終止前記錄內(nèi)存信息 9 rt.addShutdownHook(new Thread() { 10 @Override 11 public void run() { 12 StringBuffer sb = new StringBuffer(); 13 long heapMaxSize = rt.maxMemory() >> 20; 14 sb.append("最大可用內(nèi)存:" + heapMaxSize + "M\n"); 15 long total = rt.totalMemory() >> 20; 16 sb.append("對內(nèi)存大小:" + total + "M\n"); 17 long free = rt.freeMemory() >> 20; 18 sb.append("空閑內(nèi)存:" + free + "M"); 19 System.out.println(sb); 20 } 21 }); 22 // 放入40萬鍵值對 23 for (int i = 0; i < 40*10000; i++) { 24 map.put("key" + i, "vlaue" + i); 25 } 26 } 27 }?運行結(jié)果:
Exception in thread "main" 最大可用內(nèi)存:247M 對內(nèi)存大小:247M 空閑內(nèi)存:8M java.lang.OutOfMemoryError: Java heap spaceat java.util.HashMap.resize(Unknown Source)at java.util.HashMap.addEntry(Unknown Source)at java.util.HashMap.put(Unknown Source)at cn.summerchill.test.Client.main(Client.java:26)內(nèi)存溢出了....可能認為在運行時增加"-Xmx"參數(shù)設(shè)置內(nèi)存大小就可以了,這確實可以,不過浮于表面,沒有真正的從溢出的最根本原因上來解決問題.
難道的是String字符串太多了?字符串對象對象加起來撐死最多10MB,而且這里還空閑了7MB內(nèi)存,不應(yīng)該報內(nèi)存溢出.....
或者是put方法有缺陷,產(chǎn)生了內(nèi)存泄漏?不可能...這里還有7MB的內(nèi)存可用,應(yīng)該要用盡了才會出現(xiàn)內(nèi)存泄漏.....
用ArrayList做一個對比,把相同數(shù)據(jù)插入到ArrayList中看看會怎么樣,看代碼:
1 import java.util.ArrayList; 2 import java.util.List; 3 4 public class Client { 5 public static void main(String[] args) { 6 List<String> list = new ArrayList<String>(); 7 final Runtime rt = Runtime.getRuntime(); 8 // JVM終止前記錄內(nèi)存信息 9 rt.addShutdownHook(new Thread() { 10 @Override 11 public void run() { 12 StringBuffer sb = new StringBuffer(); 13 long heapMaxSize = rt.maxMemory() >> 20; 14 sb.append("最大可用內(nèi)存:" + heapMaxSize + "M\n"); 15 long total = rt.totalMemory() >> 20; 16 sb.append("對內(nèi)存大小:" + total + "M\n"); 17 long free = rt.freeMemory() >> 20; 18 sb.append("空閑內(nèi)存:" + free + "M"); 19 System.out.println(sb); 20 } 21 }); 22 // 放入40萬同樣字符串 23 for (int i = 0; i < 502654; i++) { 24 list.add("key" + i); 25 list.add("vlaue" + i); 26 } 27 } 28 }?運行輸出:
最大可用內(nèi)存:247M 對內(nèi)存大小:95M 空閑內(nèi)存:27MArrayList運行很正常,沒有出現(xiàn)內(nèi)存溢出的情況,兩個容器,容納的元素相同,數(shù)量相同,ArrayList沒有溢出,但HashMap卻溢出了,很明顯,這與HashMap內(nèi)部的處理機制有很大的關(guān)系.
HashMap在底層也是以數(shù)組方式保存元素的,其中每一個鍵值對就是一個元素 ,也就是說HashMap把鍵值對封裝成了一個Entry對象,然后再把Entry放到了數(shù)組中,我們簡單看一下Entry類:
java.util.HashMap.Entry<K, V>
1 static class Entry<K,V> implements Map.Entry<K,V> { 2 //鍵 3 final K key; 4 //值 5 V value; 6 //相同哈希碼的下一個元素 7 Entry<K,V> next; 8 9 final int hash; 10 //key,value的getter和setter方法,以及重寫的equals,hashCode,toString方法 11 }?
HashMap底層的數(shù)組變量名叫table,它是Entry類型的數(shù)組,保存的是一個個的鍵值對(在我們的例子中Entry是由兩個String類型組成的).對我們的例子來說,HashMap比ArrayList多了一次封裝,把String類型的鍵值對轉(zhuǎn)換成Entry對象后再放入數(shù)組,這就多了40萬個對象,這應(yīng)該是問題產(chǎn)生的一個原因.
我們知道HashMap的長度也是可以動態(tài)增加的,它的擴容機制與ArrayList稍有不同,其代碼如下:
if (size++ >= threshold)
resize(2 * table.length);
?在插入鍵值對時,會做長度校驗,如果大于或等于閥值(threshold變量),則數(shù)組長度增大一倍,不過,默認的閥值是多大呢?默認是當(dāng)前長度與加載因子的乘積.
threshold = (int) (newCapacity * loadFactory);
默認的加載因子(loadFactor變量)是0.75,也就是說只要HashMap的size大于數(shù)組長度的0.75倍時,就開始擴容,經(jīng)過計算得知(怎么計算,查找2的N次方大于40萬的最小值即為數(shù)組的最大長度,再乘以0.75就是最后一次擴容點,計算的結(jié)果是19),
在Map的size為393216時,符合了擴容條件,于是393216個元素準備開始大搬家,那就要首先申請一個長度為1048576(當(dāng)前長度的兩倍,2的19次方再乘以2,即2的20次方)的數(shù)組,但問題是此時剩余的內(nèi)存只有7Mb了.不足以支撐此時的運算.于是就報內(nèi)存溢出了.這是第二個原因,也是最根本的原因.
這就解釋了為什么還剩余7MB的時候就報內(nèi)存溢出了.
再考慮下ArrayList的擴容策略,它是在小于數(shù)組長度的時候才會擴容1.5倍,經(jīng)過計算得知,ArrayList的size在超過80萬后(一次加兩個元素,40萬的兩倍),最近的一次擴容會在size為1005308時,也就是說,如果程序設(shè)置了增加元素的上限為502655,同樣會報內(nèi)存溢出,因為他也要申請一個1507963長度的數(shù)組.如果沒有這么大的地方,就報錯了.
綜合來說,HashMap比ArrayList多了一個層Entry的底層對象封裝,多占用了內(nèi)存,并且它的擴容策略是2倍長度的遞增,同時還會依據(jù)閥值判斷規(guī)則進行判斷,因此相對于ArrayList來說,它就會先出現(xiàn)內(nèi)存溢出.
?
盡量讓HashMap中的元素少量并簡單.
?
//============================================
模擬List和Map的長度增長.....
1 public class Client { 2 public static void main(String[] args) { 3 //Map的最后一次擴容 4 int mapSize =16; 5 for(int i=0;i<100;i++){ 6 mapSize = mapSize * 2; 7 if(mapSize > 40*10000){ 8 System.out.println(i); 9 System.out.println("map的最后一次擴容:" + (mapSize *3/4)); 10 return; 11 } 12 } 13 14 int listSize = 10; 15 for (int i = 1; i < 1000; i++) { 16 listSize = (listSize * 3) / 2 + 1; 17 if (listSize > 40 * 10000 * 2) { 18 System.out.println("list的最后一次擴容:"+listSize); 19 return; 20 } 21 } 22 23 } 24 25 }?
轉(zhuǎn)載于:https://www.cnblogs.com/DreamDrive/p/5660173.html
總結(jié)
以上是生活随笔為你收集整理的[改善Java代码]减少HashMap中元素的数量的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UTF-8编码规则(转)
- 下一篇: Java Spring MVC