HashMap中傻傻分不清楚的那些概念
轉載自?HashMap中傻傻分不清楚的那些概念
很多人在通過閱讀源碼的方式學習Java,這是個很好的方式。而JDK的源碼自然是首選。在JDK的眾多類中,我覺得HashMap及其相關的類是設計的比較好的。很多人讀過HashMap的代碼,不知道你們有沒有和我一樣,覺得HashMap中關于容量相關的參數定義的太多了,傻傻分不清楚。
其實,這篇文章介紹的內容比較簡單,只要認真的看看HashMap的原理還是可以理解的,單獨寫一篇文章的原因是因為我后面還有幾篇關于HashMap源碼分析的文章,這些概念不熟悉的話閱讀后面的文章會很吃力。
HashMap中重要的成員變量先來看一下,HashMap中都定義了哪些成員變量。
上面是一張HashMap中主要的成員變量的圖,其中有一個是我們本文主要關注的:?size、loadFactor、threshold、DEFAULT_LOAD_FACTOR和DEFAULT_INITIAL_CAPACITY。
我們先來簡單解釋一下這些參數的含義,然后再分析他們的作用。
HashMap類中有以下主要成員變量:
transient int size;?
記錄了Map中KV對的個數
loadFactor?
裝載印子,用來衡量HashMap滿的程度。loadFactor的默認值為0.75f(static final float DEFAULT_LOAD_FACTOR = 0.75f;)。
int threshold;?
臨界值,當實際KV個數超過threshold時,HashMap會將容量擴容,threshold=容量*加載因子
除了以上這些重要成員變量外,HashMap中還有一個和他們緊密相關的概念:capacity?
容量,如果不指定,默認容量是16(static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;)
可能看完了你還是有點蒙,size和capacity之間有啥關系?為啥要定義這兩個變量。loadFactor和threshold又是干啥的?
size 和 capacityHashMap中的size和capacity之間的區別其實解釋起來也挺簡單的。我們知道,HashMap就像一個“桶”,那么capacity就是這個桶“當前”最多可以裝多少元素,而size表示這個桶已經裝了多少元素。來看下以下代碼:
Map<String, String> map = new HashMap<String, String>(); map.put("hollis", "hollischuang");Class<?> mapType = map.getClass(); Method capacity = mapType.getDeclaredMethod("capacity"); capacity.setAccessible(true); System.out.println("capacity : " + capacity.invoke(map));Field size = mapType.getDeclaredField("size"); size.setAccessible(true); System.out.println("size : " + size.get(map));我們定義了一個新的HashMap,并想其中put了一個元素,然后通過反射的方式打印capacity和size。輸出結果為:capacity : 16、size : 1
默認情況下,一個HashMap的容量(capacity)是16,設計成16的好處我在《全網把Map中的hash()分析的最透徹的文章,別無二家。》中也簡單介紹過,主要是可以使用按位與替代取模來提升hash的效率。
為什么我剛剛說capacity就是這個桶“當前”最多可以裝多少元素呢?當前怎么理解呢。其實,HashMap是具有擴容機制的。在一個HashMap第一次初始化的時候,默認情況下他的容量是16,當達到擴容條件的時候,就需要進行擴容了,會從16擴容成32。
我們知道,HashMap的重載的構造函數中,有一個是支持傳入initialCapacity的,那么我們嘗試著設置一下,看結果如何。
Map<String, String> map = new HashMap<String, String>(1); map.put("hahaha", "hollischuang");Class<?> mapType = map.getClass(); Method capacity = mapType.getDeclaredMethod("capacity"); capacity.setAccessible(true); System.out.println("capacity : " + capacity.invoke(map));Map<String, String> map = new HashMap<String, String>(7); map.put("hahaha", "hollischuang");Class<?> mapType = map.getClass(); Method capacity = mapType.getDeclaredMethod("capacity"); capacity.setAccessible(true); System.out.println("capacity : " + capacity.invoke(map));Map<String, String> map = new HashMap<String, String>(9); map.put("hahaha", "hollischuang");Class<?> mapType = map.getClass(); Method capacity = mapType.getDeclaredMethod("capacity"); capacity.setAccessible(true); System.out.println("capacity : " + capacity.invoke(map));分別執行以上3段代碼,分別輸出:capacity : 2、capacity : 8、capacity : 16。
也就是說,默認情況下HashMap的容量是16,但是,如果用戶通過構造函數指定了一個數字作為容量,那么Hash會選擇大于該數字的第一個2的冪作為容量。(1->2、7->8、9->16)
這里有一個小建議:在初始化HashMap的時候,應該盡量指定其大小。尤其是當你已知map中存放的元素個數時。(《阿里巴巴Java開發規約》)
loadFactor 和 threshold前面我們提到過,HashMap有擴容機制,就是當達到擴容條件時會進行擴容,從16擴容到32、64、128...
那么,這個擴容條件指的是什么呢?
其實,HashMap的擴容條件就是當HashMap中的元素個數(size)超過臨界值(threshold)時就會自動擴容。
在HashMap中,threshold = loadFactor * capacity。
loadFactor是裝載因子,表示HashMap滿的程度,默認值為0.75f,設置成0.75有一個好處,那就是0.75正好是3/4,而capacity又是2的冪。所以,兩個數的乘積都是整數(capacity為2也同樣)。
對于一個默認的HashMap來說,默認情況下,當其size大于12(16*0.75)時就會觸發擴容。
驗證代碼如下:
Map<String, String> map = new HashMap<>(); map.put("hollis1", "hollischuang"); map.put("hollis2", "hollischuang"); map.put("hollis3", "hollischuang"); map.put("hollis4", "hollischuang"); map.put("hollis5", "hollischuang"); map.put("hollis6", "hollischuang"); map.put("hollis7", "hollischuang"); map.put("hollis8", "hollischuang"); map.put("hollis9", "hollischuang"); map.put("hollis10", "hollischuang"); map.put("hollis11", "hollischuang"); map.put("hollis12", "hollischuang"); Class<?> mapType = map.getClass();Method capacity = mapType.getDeclaredMethod("capacity"); capacity.setAccessible(true); System.out.println("capacity : " + capacity.invoke(map));Field size = mapType.getDeclaredField("size"); size.setAccessible(true); System.out.println("size : " + size.get(map));Field threshold = mapType.getDeclaredField("threshold"); threshold.setAccessible(true); System.out.println("threshold : " + threshold.get(map));Field loadFactor = mapType.getDeclaredField("loadFactor"); loadFactor.setAccessible(true); System.out.println("loadFactor : " + loadFactor.get(map));map.put("hollis13", "hollischuang"); Method capacity = mapType.getDeclaredMethod("capacity"); capacity.setAccessible(true); System.out.println("capacity : " + capacity.invoke(map));Field size = mapType.getDeclaredField("size"); size.setAccessible(true); System.out.println("size : " + size.get(map));Field threshold = mapType.getDeclaredField("threshold"); threshold.setAccessible(true); System.out.println("threshold : " + threshold.get(map));Field loadFactor = mapType.getDeclaredField("loadFactor"); loadFactor.setAccessible(true); System.out.println("loadFactor : " + loadFactor.get(map));輸出結果:
capacity : 16 size : 12 threshold : 12 loadFactor : 0.75capacity : 32 size : 13 threshold : 24 loadFactor : 0.75當HashMap中的元素個數達到13的時候,capacity就從16擴容到32了。
HashMap中還提供了一個支持傳入initialCapacity,loadFactor兩個參數的方法,來初始化容量和裝載因子。不過,一般不建議修改loadFactor的值。
總結
HashMap中size表示當前共有多少個KV對,capacity表示當前HashMap的容量是多少,默認值是16,每次擴容都是成倍的。loadFactor是裝載因子,當Map中元素個數超過loadFactor* capacity的值時,會觸發擴容。loadFactor* capacity可以用threshold表示。
PS:文中分析基于JDK1.8.0_73
總結
以上是生活随笔為你收集整理的HashMap中傻傻分不清楚的那些概念的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手机充电接口充不进去电是怎么回事
- 下一篇: 坑爹的日志无法按天切割问题